sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True or 'always': Always quote. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.ConvertToCharset: lambda self, e: self.func( 135 "CONVERT", e.this, e.args["dest"], e.args.get("source") 136 ), 137 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 138 exp.CredentialsProperty: lambda self, 139 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 140 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 141 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 142 exp.DynamicProperty: lambda *_: "DYNAMIC", 143 exp.EmptyProperty: lambda *_: "EMPTY", 144 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 145 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 146 exp.EphemeralColumnConstraint: lambda self, 147 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 148 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 149 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 150 exp.Except: lambda self, e: self.set_operations(e), 151 exp.ExternalProperty: lambda *_: "EXTERNAL", 152 exp.Floor: lambda self, e: self.ceil_floor(e), 153 exp.Get: lambda self, e: self.get_put_sql(e), 154 exp.GlobalProperty: lambda *_: "GLOBAL", 155 exp.HeapProperty: lambda *_: "HEAP", 156 exp.IcebergProperty: lambda *_: "ICEBERG", 157 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 158 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 159 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 160 exp.Intersect: lambda self, e: self.set_operations(e), 161 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 162 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 163 exp.LanguageProperty: lambda self, e: self.naked_property(e), 164 exp.LocationProperty: lambda self, e: self.naked_property(e), 165 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 166 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 167 exp.NonClusteredColumnConstraint: lambda self, 168 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 169 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 170 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 171 exp.OnCommitProperty: lambda _, 172 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 173 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 174 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 175 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 176 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 177 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 178 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 179 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 180 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 181 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 182 exp.ProjectionPolicyColumnConstraint: lambda self, 183 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 184 exp.Put: lambda self, e: self.get_put_sql(e), 185 exp.RemoteWithConnectionModelProperty: lambda self, 186 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 187 exp.ReturnsProperty: lambda self, e: ( 188 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 189 ), 190 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 191 exp.SecureProperty: lambda *_: "SECURE", 192 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 193 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 194 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 195 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 196 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 197 exp.SqlReadWriteProperty: lambda _, e: e.name, 198 exp.SqlSecurityProperty: lambda _, 199 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 200 exp.StabilityProperty: lambda _, e: e.name, 201 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 202 exp.StreamingTableProperty: lambda *_: "STREAMING", 203 exp.StrictProperty: lambda *_: "STRICT", 204 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 205 exp.TableColumn: lambda self, e: self.sql(e.this), 206 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 207 exp.TemporaryProperty: lambda *_: "TEMPORARY", 208 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 209 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 210 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 211 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 212 exp.TransientProperty: lambda *_: "TRANSIENT", 213 exp.Union: lambda self, e: self.set_operations(e), 214 exp.UnloggedProperty: lambda *_: "UNLOGGED", 215 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 216 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 217 exp.Uuid: lambda *_: "UUID()", 218 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 219 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 220 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 221 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 222 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 223 exp.VolatileProperty: lambda *_: "VOLATILE", 224 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 225 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 226 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 227 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 228 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 229 exp.ForceProperty: lambda *_: "FORCE", 230 } 231 232 # Whether null ordering is supported in order by 233 # True: Full Support, None: No support, False: No support for certain cases 234 # such as window specifications, aggregate functions etc 235 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 236 237 # Whether ignore nulls is inside the agg or outside. 238 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 239 IGNORE_NULLS_IN_FUNC = False 240 241 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 242 LOCKING_READS_SUPPORTED = False 243 244 # Whether the EXCEPT and INTERSECT operations can return duplicates 245 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 246 247 # Wrap derived values in parens, usually standard but spark doesn't support it 248 WRAP_DERIVED_VALUES = True 249 250 # Whether create function uses an AS before the RETURN 251 CREATE_FUNCTION_RETURN_AS = True 252 253 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 254 MATCHED_BY_SOURCE = True 255 256 # Whether the INTERVAL expression works only with values like '1 day' 257 SINGLE_STRING_INTERVAL = False 258 259 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 260 INTERVAL_ALLOWS_PLURAL_FORM = True 261 262 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 263 LIMIT_FETCH = "ALL" 264 265 # Whether limit and fetch allows expresions or just limits 266 LIMIT_ONLY_LITERALS = False 267 268 # Whether a table is allowed to be renamed with a db 269 RENAME_TABLE_WITH_DB = True 270 271 # The separator for grouping sets and rollups 272 GROUPINGS_SEP = "," 273 274 # The string used for creating an index on a table 275 INDEX_ON = "ON" 276 277 # Whether join hints should be generated 278 JOIN_HINTS = True 279 280 # Whether table hints should be generated 281 TABLE_HINTS = True 282 283 # Whether query hints should be generated 284 QUERY_HINTS = True 285 286 # What kind of separator to use for query hints 287 QUERY_HINT_SEP = ", " 288 289 # Whether comparing against booleans (e.g. x IS TRUE) is supported 290 IS_BOOL_ALLOWED = True 291 292 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 293 DUPLICATE_KEY_UPDATE_WITH_SET = True 294 295 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 296 LIMIT_IS_TOP = False 297 298 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 299 RETURNING_END = True 300 301 # Whether to generate an unquoted value for EXTRACT's date part argument 302 EXTRACT_ALLOWS_QUOTES = True 303 304 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 305 TZ_TO_WITH_TIME_ZONE = False 306 307 # Whether the NVL2 function is supported 308 NVL2_SUPPORTED = True 309 310 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 311 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 312 313 # Whether VALUES statements can be used as derived tables. 314 # MySQL 5 and Redshift do not allow this, so when False, it will convert 315 # SELECT * VALUES into SELECT UNION 316 VALUES_AS_TABLE = True 317 318 # Whether the word COLUMN is included when adding a column with ALTER TABLE 319 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 320 321 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 322 UNNEST_WITH_ORDINALITY = True 323 324 # Whether FILTER (WHERE cond) can be used for conditional aggregation 325 AGGREGATE_FILTER_SUPPORTED = True 326 327 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 328 SEMI_ANTI_JOIN_WITH_SIDE = True 329 330 # Whether to include the type of a computed column in the CREATE DDL 331 COMPUTED_COLUMN_WITH_TYPE = True 332 333 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 334 SUPPORTS_TABLE_COPY = True 335 336 # Whether parentheses are required around the table sample's expression 337 TABLESAMPLE_REQUIRES_PARENS = True 338 339 # Whether a table sample clause's size needs to be followed by the ROWS keyword 340 TABLESAMPLE_SIZE_IS_ROWS = True 341 342 # The keyword(s) to use when generating a sample clause 343 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 344 345 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 346 TABLESAMPLE_WITH_METHOD = True 347 348 # The keyword to use when specifying the seed of a sample clause 349 TABLESAMPLE_SEED_KEYWORD = "SEED" 350 351 # Whether COLLATE is a function instead of a binary operator 352 COLLATE_IS_FUNC = False 353 354 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 355 DATA_TYPE_SPECIFIERS_ALLOWED = False 356 357 # Whether conditions require booleans WHERE x = 0 vs WHERE x 358 ENSURE_BOOLS = False 359 360 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 361 CTE_RECURSIVE_KEYWORD_REQUIRED = True 362 363 # Whether CONCAT requires >1 arguments 364 SUPPORTS_SINGLE_ARG_CONCAT = True 365 366 # Whether LAST_DAY function supports a date part argument 367 LAST_DAY_SUPPORTS_DATE_PART = True 368 369 # Whether named columns are allowed in table aliases 370 SUPPORTS_TABLE_ALIAS_COLUMNS = True 371 372 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 373 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 374 375 # What delimiter to use for separating JSON key/value pairs 376 JSON_KEY_VALUE_PAIR_SEP = ":" 377 378 # INSERT OVERWRITE TABLE x override 379 INSERT_OVERWRITE = " OVERWRITE TABLE" 380 381 # Whether the SELECT .. INTO syntax is used instead of CTAS 382 SUPPORTS_SELECT_INTO = False 383 384 # Whether UNLOGGED tables can be created 385 SUPPORTS_UNLOGGED_TABLES = False 386 387 # Whether the CREATE TABLE LIKE statement is supported 388 SUPPORTS_CREATE_TABLE_LIKE = True 389 390 # Whether the LikeProperty needs to be specified inside of the schema clause 391 LIKE_PROPERTY_INSIDE_SCHEMA = False 392 393 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 394 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 395 MULTI_ARG_DISTINCT = True 396 397 # Whether the JSON extraction operators expect a value of type JSON 398 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 399 400 # Whether bracketed keys like ["foo"] are supported in JSON paths 401 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 402 403 # Whether to escape keys using single quotes in JSON paths 404 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 405 406 # The JSONPathPart expressions supported by this dialect 407 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 408 409 # Whether any(f(x) for x in array) can be implemented by this dialect 410 CAN_IMPLEMENT_ARRAY_ANY = False 411 412 # Whether the function TO_NUMBER is supported 413 SUPPORTS_TO_NUMBER = True 414 415 # Whether EXCLUDE in window specification is supported 416 SUPPORTS_WINDOW_EXCLUDE = False 417 418 # Whether or not set op modifiers apply to the outer set op or select. 419 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 420 # True means limit 1 happens after the set op, False means it it happens on y. 421 SET_OP_MODIFIERS = True 422 423 # Whether parameters from COPY statement are wrapped in parentheses 424 COPY_PARAMS_ARE_WRAPPED = True 425 426 # Whether values of params are set with "=" token or empty space 427 COPY_PARAMS_EQ_REQUIRED = False 428 429 # Whether COPY statement has INTO keyword 430 COPY_HAS_INTO_KEYWORD = True 431 432 # Whether the conditional TRY(expression) function is supported 433 TRY_SUPPORTED = True 434 435 # Whether the UESCAPE syntax in unicode strings is supported 436 SUPPORTS_UESCAPE = True 437 438 # Function used to replace escaped unicode codes in unicode strings 439 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 440 441 # The keyword to use when generating a star projection with excluded columns 442 STAR_EXCEPT = "EXCEPT" 443 444 # The HEX function name 445 HEX_FUNC = "HEX" 446 447 # The keywords to use when prefixing & separating WITH based properties 448 WITH_PROPERTIES_PREFIX = "WITH" 449 450 # Whether to quote the generated expression of exp.JsonPath 451 QUOTE_JSON_PATH = True 452 453 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 454 PAD_FILL_PATTERN_IS_REQUIRED = False 455 456 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 457 SUPPORTS_EXPLODING_PROJECTIONS = True 458 459 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 460 ARRAY_CONCAT_IS_VAR_LEN = True 461 462 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 463 SUPPORTS_CONVERT_TIMEZONE = False 464 465 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 466 SUPPORTS_MEDIAN = True 467 468 # Whether UNIX_SECONDS(timestamp) is supported 469 SUPPORTS_UNIX_SECONDS = False 470 471 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 472 ALTER_SET_WRAPPED = False 473 474 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 475 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 476 # TODO: The normalization should be done by default once we've tested it across all dialects. 477 NORMALIZE_EXTRACT_DATE_PARTS = False 478 479 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 480 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 481 482 # The function name of the exp.ArraySize expression 483 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 484 485 # The syntax to use when altering the type of a column 486 ALTER_SET_TYPE = "SET DATA TYPE" 487 488 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 489 # None -> Doesn't support it at all 490 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 491 # True (Postgres) -> Explicitly requires it 492 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 493 494 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 495 SUPPORTS_DECODE_CASE = True 496 497 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 498 SUPPORTS_BETWEEN_FLAGS = False 499 500 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 501 SUPPORTS_LIKE_QUANTIFIERS = True 502 503 TYPE_MAPPING = { 504 exp.DataType.Type.DATETIME2: "TIMESTAMP", 505 exp.DataType.Type.NCHAR: "CHAR", 506 exp.DataType.Type.NVARCHAR: "VARCHAR", 507 exp.DataType.Type.MEDIUMTEXT: "TEXT", 508 exp.DataType.Type.LONGTEXT: "TEXT", 509 exp.DataType.Type.TINYTEXT: "TEXT", 510 exp.DataType.Type.BLOB: "VARBINARY", 511 exp.DataType.Type.MEDIUMBLOB: "BLOB", 512 exp.DataType.Type.LONGBLOB: "BLOB", 513 exp.DataType.Type.TINYBLOB: "BLOB", 514 exp.DataType.Type.INET: "INET", 515 exp.DataType.Type.ROWVERSION: "VARBINARY", 516 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 517 } 518 519 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 520 521 TIME_PART_SINGULARS = { 522 "MICROSECONDS": "MICROSECOND", 523 "SECONDS": "SECOND", 524 "MINUTES": "MINUTE", 525 "HOURS": "HOUR", 526 "DAYS": "DAY", 527 "WEEKS": "WEEK", 528 "MONTHS": "MONTH", 529 "QUARTERS": "QUARTER", 530 "YEARS": "YEAR", 531 } 532 533 AFTER_HAVING_MODIFIER_TRANSFORMS = { 534 "cluster": lambda self, e: self.sql(e, "cluster"), 535 "distribute": lambda self, e: self.sql(e, "distribute"), 536 "sort": lambda self, e: self.sql(e, "sort"), 537 "windows": lambda self, e: ( 538 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 539 if e.args.get("windows") 540 else "" 541 ), 542 "qualify": lambda self, e: self.sql(e, "qualify"), 543 } 544 545 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 546 547 STRUCT_DELIMITER = ("<", ">") 548 549 PARAMETER_TOKEN = "@" 550 NAMED_PLACEHOLDER_TOKEN = ":" 551 552 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 553 554 PROPERTIES_LOCATION = { 555 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 557 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 561 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 563 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 566 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 570 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 572 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 573 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 575 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 579 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 583 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 584 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 585 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 586 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 587 exp.HeapProperty: exp.Properties.Location.POST_WITH, 588 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 590 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 593 exp.JournalProperty: exp.Properties.Location.POST_NAME, 594 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 595 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 599 exp.LogProperty: exp.Properties.Location.POST_NAME, 600 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 601 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 602 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 603 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 605 exp.Order: exp.Properties.Location.POST_SCHEMA, 606 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 608 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 610 exp.Property: exp.Properties.Location.POST_WITH, 611 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 619 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 621 exp.Set: exp.Properties.Location.POST_SCHEMA, 622 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.SetProperty: exp.Properties.Location.POST_CREATE, 624 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 626 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 627 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 630 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 631 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 633 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.Tags: exp.Properties.Location.POST_WITH, 635 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 636 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 638 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 640 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 641 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 644 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 645 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 646 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 647 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 650 } 651 652 # Keywords that can't be used as unquoted identifier names 653 RESERVED_KEYWORDS: t.Set[str] = set() 654 655 # Expressions whose comments are separated from them for better formatting 656 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 657 exp.Command, 658 exp.Create, 659 exp.Describe, 660 exp.Delete, 661 exp.Drop, 662 exp.From, 663 exp.Insert, 664 exp.Join, 665 exp.MultitableInserts, 666 exp.Order, 667 exp.Group, 668 exp.Having, 669 exp.Select, 670 exp.SetOperation, 671 exp.Update, 672 exp.Where, 673 exp.With, 674 ) 675 676 # Expressions that should not have their comments generated in maybe_comment 677 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 678 exp.Binary, 679 exp.SetOperation, 680 ) 681 682 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 683 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 684 exp.Column, 685 exp.Literal, 686 exp.Neg, 687 exp.Paren, 688 ) 689 690 PARAMETERIZABLE_TEXT_TYPES = { 691 exp.DataType.Type.NVARCHAR, 692 exp.DataType.Type.VARCHAR, 693 exp.DataType.Type.CHAR, 694 exp.DataType.Type.NCHAR, 695 } 696 697 # Expressions that need to have all CTEs under them bubbled up to them 698 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 699 700 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 701 702 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 703 704 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 705 706 __slots__ = ( 707 "pretty", 708 "identify", 709 "normalize", 710 "pad", 711 "_indent", 712 "normalize_functions", 713 "unsupported_level", 714 "max_unsupported", 715 "leading_comma", 716 "max_text_width", 717 "comments", 718 "dialect", 719 "unsupported_messages", 720 "_escaped_quote_end", 721 "_escaped_identifier_end", 722 "_next_name", 723 "_identifier_start", 724 "_identifier_end", 725 "_quote_json_path_key_using_brackets", 726 ) 727 728 def __init__( 729 self, 730 pretty: t.Optional[bool] = None, 731 identify: str | bool = False, 732 normalize: bool = False, 733 pad: int = 2, 734 indent: int = 2, 735 normalize_functions: t.Optional[str | bool] = None, 736 unsupported_level: ErrorLevel = ErrorLevel.WARN, 737 max_unsupported: int = 3, 738 leading_comma: bool = False, 739 max_text_width: int = 80, 740 comments: bool = True, 741 dialect: DialectType = None, 742 ): 743 import sqlglot 744 from sqlglot.dialects import Dialect 745 746 self.pretty = pretty if pretty is not None else sqlglot.pretty 747 self.identify = identify 748 self.normalize = normalize 749 self.pad = pad 750 self._indent = indent 751 self.unsupported_level = unsupported_level 752 self.max_unsupported = max_unsupported 753 self.leading_comma = leading_comma 754 self.max_text_width = max_text_width 755 self.comments = comments 756 self.dialect = Dialect.get_or_raise(dialect) 757 758 # This is both a Dialect property and a Generator argument, so we prioritize the latter 759 self.normalize_functions = ( 760 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 761 ) 762 763 self.unsupported_messages: t.List[str] = [] 764 self._escaped_quote_end: str = ( 765 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 766 ) 767 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 768 769 self._next_name = name_sequence("_t") 770 771 self._identifier_start = self.dialect.IDENTIFIER_START 772 self._identifier_end = self.dialect.IDENTIFIER_END 773 774 self._quote_json_path_key_using_brackets = True 775 776 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 777 """ 778 Generates the SQL string corresponding to the given syntax tree. 779 780 Args: 781 expression: The syntax tree. 782 copy: Whether to copy the expression. The generator performs mutations so 783 it is safer to copy. 784 785 Returns: 786 The SQL string corresponding to `expression`. 787 """ 788 if copy: 789 expression = expression.copy() 790 791 expression = self.preprocess(expression) 792 793 self.unsupported_messages = [] 794 sql = self.sql(expression).strip() 795 796 if self.pretty: 797 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 798 799 if self.unsupported_level == ErrorLevel.IGNORE: 800 return sql 801 802 if self.unsupported_level == ErrorLevel.WARN: 803 for msg in self.unsupported_messages: 804 logger.warning(msg) 805 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 806 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 807 808 return sql 809 810 def preprocess(self, expression: exp.Expression) -> exp.Expression: 811 """Apply generic preprocessing transformations to a given expression.""" 812 expression = self._move_ctes_to_top_level(expression) 813 814 if self.ENSURE_BOOLS: 815 from sqlglot.transforms import ensure_bools 816 817 expression = ensure_bools(expression) 818 819 return expression 820 821 def _move_ctes_to_top_level(self, expression: E) -> E: 822 if ( 823 not expression.parent 824 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 825 and any(node.parent is not expression for node in expression.find_all(exp.With)) 826 ): 827 from sqlglot.transforms import move_ctes_to_top_level 828 829 expression = move_ctes_to_top_level(expression) 830 return expression 831 832 def unsupported(self, message: str) -> None: 833 if self.unsupported_level == ErrorLevel.IMMEDIATE: 834 raise UnsupportedError(message) 835 self.unsupported_messages.append(message) 836 837 def sep(self, sep: str = " ") -> str: 838 return f"{sep.strip()}\n" if self.pretty else sep 839 840 def seg(self, sql: str, sep: str = " ") -> str: 841 return f"{self.sep(sep)}{sql}" 842 843 def sanitize_comment(self, comment: str) -> str: 844 comment = " " + comment if comment[0].strip() else comment 845 comment = comment + " " if comment[-1].strip() else comment 846 847 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 848 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 849 comment = comment.replace("*/", "* /") 850 851 return comment 852 853 def maybe_comment( 854 self, 855 sql: str, 856 expression: t.Optional[exp.Expression] = None, 857 comments: t.Optional[t.List[str]] = None, 858 separated: bool = False, 859 ) -> str: 860 comments = ( 861 ((expression and expression.comments) if comments is None else comments) # type: ignore 862 if self.comments 863 else None 864 ) 865 866 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 867 return sql 868 869 comments_sql = " ".join( 870 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 871 ) 872 873 if not comments_sql: 874 return sql 875 876 comments_sql = self._replace_line_breaks(comments_sql) 877 878 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 879 return ( 880 f"{self.sep()}{comments_sql}{sql}" 881 if not sql or sql[0].isspace() 882 else f"{comments_sql}{self.sep()}{sql}" 883 ) 884 885 return f"{sql} {comments_sql}" 886 887 def wrap(self, expression: exp.Expression | str) -> str: 888 this_sql = ( 889 self.sql(expression) 890 if isinstance(expression, exp.UNWRAPPED_QUERIES) 891 else self.sql(expression, "this") 892 ) 893 if not this_sql: 894 return "()" 895 896 this_sql = self.indent(this_sql, level=1, pad=0) 897 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 898 899 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 900 original = self.identify 901 self.identify = False 902 result = func(*args, **kwargs) 903 self.identify = original 904 return result 905 906 def normalize_func(self, name: str) -> str: 907 if self.normalize_functions == "upper" or self.normalize_functions is True: 908 return name.upper() 909 if self.normalize_functions == "lower": 910 return name.lower() 911 return name 912 913 def indent( 914 self, 915 sql: str, 916 level: int = 0, 917 pad: t.Optional[int] = None, 918 skip_first: bool = False, 919 skip_last: bool = False, 920 ) -> str: 921 if not self.pretty or not sql: 922 return sql 923 924 pad = self.pad if pad is None else pad 925 lines = sql.split("\n") 926 927 return "\n".join( 928 ( 929 line 930 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 931 else f"{' ' * (level * self._indent + pad)}{line}" 932 ) 933 for i, line in enumerate(lines) 934 ) 935 936 def sql( 937 self, 938 expression: t.Optional[str | exp.Expression], 939 key: t.Optional[str] = None, 940 comment: bool = True, 941 ) -> str: 942 if not expression: 943 return "" 944 945 if isinstance(expression, str): 946 return expression 947 948 if key: 949 value = expression.args.get(key) 950 if value: 951 return self.sql(value) 952 return "" 953 954 transform = self.TRANSFORMS.get(expression.__class__) 955 956 if callable(transform): 957 sql = transform(self, expression) 958 elif isinstance(expression, exp.Expression): 959 exp_handler_name = f"{expression.key}_sql" 960 961 if hasattr(self, exp_handler_name): 962 sql = getattr(self, exp_handler_name)(expression) 963 elif isinstance(expression, exp.Func): 964 sql = self.function_fallback_sql(expression) 965 elif isinstance(expression, exp.Property): 966 sql = self.property_sql(expression) 967 else: 968 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 969 else: 970 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 971 972 return self.maybe_comment(sql, expression) if self.comments and comment else sql 973 974 def uncache_sql(self, expression: exp.Uncache) -> str: 975 table = self.sql(expression, "this") 976 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 977 return f"UNCACHE TABLE{exists_sql} {table}" 978 979 def cache_sql(self, expression: exp.Cache) -> str: 980 lazy = " LAZY" if expression.args.get("lazy") else "" 981 table = self.sql(expression, "this") 982 options = expression.args.get("options") 983 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 984 sql = self.sql(expression, "expression") 985 sql = f" AS{self.sep()}{sql}" if sql else "" 986 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 987 return self.prepend_ctes(expression, sql) 988 989 def characterset_sql(self, expression: exp.CharacterSet) -> str: 990 if isinstance(expression.parent, exp.Cast): 991 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 992 default = "DEFAULT " if expression.args.get("default") else "" 993 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 994 995 def column_parts(self, expression: exp.Column) -> str: 996 return ".".join( 997 self.sql(part) 998 for part in ( 999 expression.args.get("catalog"), 1000 expression.args.get("db"), 1001 expression.args.get("table"), 1002 expression.args.get("this"), 1003 ) 1004 if part 1005 ) 1006 1007 def column_sql(self, expression: exp.Column) -> str: 1008 join_mark = " (+)" if expression.args.get("join_mark") else "" 1009 1010 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1011 join_mark = "" 1012 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1013 1014 return f"{self.column_parts(expression)}{join_mark}" 1015 1016 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1017 this = self.sql(expression, "this") 1018 this = f" {this}" if this else "" 1019 position = self.sql(expression, "position") 1020 return f"{position}{this}" 1021 1022 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1023 column = self.sql(expression, "this") 1024 kind = self.sql(expression, "kind") 1025 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1026 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1027 kind = f"{sep}{kind}" if kind else "" 1028 constraints = f" {constraints}" if constraints else "" 1029 position = self.sql(expression, "position") 1030 position = f" {position}" if position else "" 1031 1032 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1033 kind = "" 1034 1035 return f"{exists}{column}{kind}{constraints}{position}" 1036 1037 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1038 this = self.sql(expression, "this") 1039 kind_sql = self.sql(expression, "kind").strip() 1040 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1041 1042 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1043 this = self.sql(expression, "this") 1044 if expression.args.get("not_null"): 1045 persisted = " PERSISTED NOT NULL" 1046 elif expression.args.get("persisted"): 1047 persisted = " PERSISTED" 1048 else: 1049 persisted = "" 1050 1051 return f"AS {this}{persisted}" 1052 1053 def autoincrementcolumnconstraint_sql(self, _) -> str: 1054 return self.token_sql(TokenType.AUTO_INCREMENT) 1055 1056 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1057 if isinstance(expression.this, list): 1058 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1059 else: 1060 this = self.sql(expression, "this") 1061 1062 return f"COMPRESS {this}" 1063 1064 def generatedasidentitycolumnconstraint_sql( 1065 self, expression: exp.GeneratedAsIdentityColumnConstraint 1066 ) -> str: 1067 this = "" 1068 if expression.this is not None: 1069 on_null = " ON NULL" if expression.args.get("on_null") else "" 1070 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1071 1072 start = expression.args.get("start") 1073 start = f"START WITH {start}" if start else "" 1074 increment = expression.args.get("increment") 1075 increment = f" INCREMENT BY {increment}" if increment else "" 1076 minvalue = expression.args.get("minvalue") 1077 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1078 maxvalue = expression.args.get("maxvalue") 1079 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1080 cycle = expression.args.get("cycle") 1081 cycle_sql = "" 1082 1083 if cycle is not None: 1084 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1085 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1086 1087 sequence_opts = "" 1088 if start or increment or cycle_sql: 1089 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1090 sequence_opts = f" ({sequence_opts.strip()})" 1091 1092 expr = self.sql(expression, "expression") 1093 expr = f"({expr})" if expr else "IDENTITY" 1094 1095 return f"GENERATED{this} AS {expr}{sequence_opts}" 1096 1097 def generatedasrowcolumnconstraint_sql( 1098 self, expression: exp.GeneratedAsRowColumnConstraint 1099 ) -> str: 1100 start = "START" if expression.args.get("start") else "END" 1101 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1102 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1103 1104 def periodforsystemtimeconstraint_sql( 1105 self, expression: exp.PeriodForSystemTimeConstraint 1106 ) -> str: 1107 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1108 1109 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1110 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1111 1112 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1113 desc = expression.args.get("desc") 1114 if desc is not None: 1115 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1116 options = self.expressions(expression, key="options", flat=True, sep=" ") 1117 options = f" {options}" if options else "" 1118 return f"PRIMARY KEY{options}" 1119 1120 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1121 this = self.sql(expression, "this") 1122 this = f" {this}" if this else "" 1123 index_type = expression.args.get("index_type") 1124 index_type = f" USING {index_type}" if index_type else "" 1125 on_conflict = self.sql(expression, "on_conflict") 1126 on_conflict = f" {on_conflict}" if on_conflict else "" 1127 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1128 options = self.expressions(expression, key="options", flat=True, sep=" ") 1129 options = f" {options}" if options else "" 1130 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1131 1132 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1133 return self.sql(expression, "this") 1134 1135 def create_sql(self, expression: exp.Create) -> str: 1136 kind = self.sql(expression, "kind") 1137 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1138 properties = expression.args.get("properties") 1139 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1140 1141 this = self.createable_sql(expression, properties_locs) 1142 1143 properties_sql = "" 1144 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1145 exp.Properties.Location.POST_WITH 1146 ): 1147 props_ast = exp.Properties( 1148 expressions=[ 1149 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1150 *properties_locs[exp.Properties.Location.POST_WITH], 1151 ] 1152 ) 1153 props_ast.parent = expression 1154 properties_sql = self.sql(props_ast) 1155 1156 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1157 properties_sql = self.sep() + properties_sql 1158 elif not self.pretty: 1159 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1160 properties_sql = f" {properties_sql}" 1161 1162 begin = " BEGIN" if expression.args.get("begin") else "" 1163 end = " END" if expression.args.get("end") else "" 1164 1165 expression_sql = self.sql(expression, "expression") 1166 if expression_sql: 1167 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1168 1169 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1170 postalias_props_sql = "" 1171 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1172 postalias_props_sql = self.properties( 1173 exp.Properties( 1174 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1175 ), 1176 wrapped=False, 1177 ) 1178 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1179 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1180 1181 postindex_props_sql = "" 1182 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1183 postindex_props_sql = self.properties( 1184 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1185 wrapped=False, 1186 prefix=" ", 1187 ) 1188 1189 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1190 indexes = f" {indexes}" if indexes else "" 1191 index_sql = indexes + postindex_props_sql 1192 1193 replace = " OR REPLACE" if expression.args.get("replace") else "" 1194 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1195 unique = " UNIQUE" if expression.args.get("unique") else "" 1196 1197 clustered = expression.args.get("clustered") 1198 if clustered is None: 1199 clustered_sql = "" 1200 elif clustered: 1201 clustered_sql = " CLUSTERED COLUMNSTORE" 1202 else: 1203 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1204 1205 postcreate_props_sql = "" 1206 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1207 postcreate_props_sql = self.properties( 1208 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1209 sep=" ", 1210 prefix=" ", 1211 wrapped=False, 1212 ) 1213 1214 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1215 1216 postexpression_props_sql = "" 1217 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1218 postexpression_props_sql = self.properties( 1219 exp.Properties( 1220 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1221 ), 1222 sep=" ", 1223 prefix=" ", 1224 wrapped=False, 1225 ) 1226 1227 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1228 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1229 no_schema_binding = ( 1230 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1231 ) 1232 1233 clone = self.sql(expression, "clone") 1234 clone = f" {clone}" if clone else "" 1235 1236 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1237 properties_expression = f"{expression_sql}{properties_sql}" 1238 else: 1239 properties_expression = f"{properties_sql}{expression_sql}" 1240 1241 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1242 return self.prepend_ctes(expression, expression_sql) 1243 1244 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1245 start = self.sql(expression, "start") 1246 start = f"START WITH {start}" if start else "" 1247 increment = self.sql(expression, "increment") 1248 increment = f" INCREMENT BY {increment}" if increment else "" 1249 minvalue = self.sql(expression, "minvalue") 1250 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1251 maxvalue = self.sql(expression, "maxvalue") 1252 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1253 owned = self.sql(expression, "owned") 1254 owned = f" OWNED BY {owned}" if owned else "" 1255 1256 cache = expression.args.get("cache") 1257 if cache is None: 1258 cache_str = "" 1259 elif cache is True: 1260 cache_str = " CACHE" 1261 else: 1262 cache_str = f" CACHE {cache}" 1263 1264 options = self.expressions(expression, key="options", flat=True, sep=" ") 1265 options = f" {options}" if options else "" 1266 1267 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1268 1269 def clone_sql(self, expression: exp.Clone) -> str: 1270 this = self.sql(expression, "this") 1271 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1272 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1273 return f"{shallow}{keyword} {this}" 1274 1275 def describe_sql(self, expression: exp.Describe) -> str: 1276 style = expression.args.get("style") 1277 style = f" {style}" if style else "" 1278 partition = self.sql(expression, "partition") 1279 partition = f" {partition}" if partition else "" 1280 format = self.sql(expression, "format") 1281 format = f" {format}" if format else "" 1282 1283 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1284 1285 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1286 tag = self.sql(expression, "tag") 1287 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1288 1289 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1290 with_ = self.sql(expression, "with") 1291 if with_: 1292 sql = f"{with_}{self.sep()}{sql}" 1293 return sql 1294 1295 def with_sql(self, expression: exp.With) -> str: 1296 sql = self.expressions(expression, flat=True) 1297 recursive = ( 1298 "RECURSIVE " 1299 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1300 else "" 1301 ) 1302 search = self.sql(expression, "search") 1303 search = f" {search}" if search else "" 1304 1305 return f"WITH {recursive}{sql}{search}" 1306 1307 def cte_sql(self, expression: exp.CTE) -> str: 1308 alias = expression.args.get("alias") 1309 if alias: 1310 alias.add_comments(expression.pop_comments()) 1311 1312 alias_sql = self.sql(expression, "alias") 1313 1314 materialized = expression.args.get("materialized") 1315 if materialized is False: 1316 materialized = "NOT MATERIALIZED " 1317 elif materialized: 1318 materialized = "MATERIALIZED " 1319 1320 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1321 1322 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1323 alias = self.sql(expression, "this") 1324 columns = self.expressions(expression, key="columns", flat=True) 1325 columns = f"({columns})" if columns else "" 1326 1327 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1328 columns = "" 1329 self.unsupported("Named columns are not supported in table alias.") 1330 1331 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1332 alias = self._next_name() 1333 1334 return f"{alias}{columns}" 1335 1336 def bitstring_sql(self, expression: exp.BitString) -> str: 1337 this = self.sql(expression, "this") 1338 if self.dialect.BIT_START: 1339 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1340 return f"{int(this, 2)}" 1341 1342 def hexstring_sql( 1343 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1344 ) -> str: 1345 this = self.sql(expression, "this") 1346 is_integer_type = expression.args.get("is_integer") 1347 1348 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1349 not self.dialect.HEX_START and not binary_function_repr 1350 ): 1351 # Integer representation will be returned if: 1352 # - The read dialect treats the hex value as integer literal but not the write 1353 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1354 return f"{int(this, 16)}" 1355 1356 if not is_integer_type: 1357 # Read dialect treats the hex value as BINARY/BLOB 1358 if binary_function_repr: 1359 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1360 return self.func(binary_function_repr, exp.Literal.string(this)) 1361 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1362 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1363 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1364 1365 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1366 1367 def bytestring_sql(self, expression: exp.ByteString) -> str: 1368 this = self.sql(expression, "this") 1369 if self.dialect.BYTE_START: 1370 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1371 return this 1372 1373 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1374 this = self.sql(expression, "this") 1375 escape = expression.args.get("escape") 1376 1377 if self.dialect.UNICODE_START: 1378 escape_substitute = r"\\\1" 1379 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1380 else: 1381 escape_substitute = r"\\u\1" 1382 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1383 1384 if escape: 1385 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1386 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1387 else: 1388 escape_pattern = ESCAPED_UNICODE_RE 1389 escape_sql = "" 1390 1391 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1392 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1393 1394 return f"{left_quote}{this}{right_quote}{escape_sql}" 1395 1396 def rawstring_sql(self, expression: exp.RawString) -> str: 1397 string = expression.this 1398 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1399 string = string.replace("\\", "\\\\") 1400 1401 string = self.escape_str(string, escape_backslash=False) 1402 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1403 1404 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1405 this = self.sql(expression, "this") 1406 specifier = self.sql(expression, "expression") 1407 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1408 return f"{this}{specifier}" 1409 1410 def datatype_sql(self, expression: exp.DataType) -> str: 1411 nested = "" 1412 values = "" 1413 interior = self.expressions(expression, flat=True) 1414 1415 type_value = expression.this 1416 if type_value in self.UNSUPPORTED_TYPES: 1417 self.unsupported( 1418 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1419 ) 1420 1421 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1422 type_sql = self.sql(expression, "kind") 1423 else: 1424 type_sql = ( 1425 self.TYPE_MAPPING.get(type_value, type_value.value) 1426 if isinstance(type_value, exp.DataType.Type) 1427 else type_value 1428 ) 1429 1430 if interior: 1431 if expression.args.get("nested"): 1432 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1433 if expression.args.get("values") is not None: 1434 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1435 values = self.expressions(expression, key="values", flat=True) 1436 values = f"{delimiters[0]}{values}{delimiters[1]}" 1437 elif type_value == exp.DataType.Type.INTERVAL: 1438 nested = f" {interior}" 1439 else: 1440 nested = f"({interior})" 1441 1442 type_sql = f"{type_sql}{nested}{values}" 1443 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1444 exp.DataType.Type.TIMETZ, 1445 exp.DataType.Type.TIMESTAMPTZ, 1446 ): 1447 type_sql = f"{type_sql} WITH TIME ZONE" 1448 1449 return type_sql 1450 1451 def directory_sql(self, expression: exp.Directory) -> str: 1452 local = "LOCAL " if expression.args.get("local") else "" 1453 row_format = self.sql(expression, "row_format") 1454 row_format = f" {row_format}" if row_format else "" 1455 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1456 1457 def delete_sql(self, expression: exp.Delete) -> str: 1458 this = self.sql(expression, "this") 1459 this = f" FROM {this}" if this else "" 1460 using = self.sql(expression, "using") 1461 using = f" USING {using}" if using else "" 1462 cluster = self.sql(expression, "cluster") 1463 cluster = f" {cluster}" if cluster else "" 1464 where = self.sql(expression, "where") 1465 returning = self.sql(expression, "returning") 1466 limit = self.sql(expression, "limit") 1467 tables = self.expressions(expression, key="tables") 1468 tables = f" {tables}" if tables else "" 1469 if self.RETURNING_END: 1470 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1471 else: 1472 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1473 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1474 1475 def drop_sql(self, expression: exp.Drop) -> str: 1476 this = self.sql(expression, "this") 1477 expressions = self.expressions(expression, flat=True) 1478 expressions = f" ({expressions})" if expressions else "" 1479 kind = expression.args["kind"] 1480 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1481 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1482 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1483 on_cluster = self.sql(expression, "cluster") 1484 on_cluster = f" {on_cluster}" if on_cluster else "" 1485 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1486 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1487 cascade = " CASCADE" if expression.args.get("cascade") else "" 1488 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1489 purge = " PURGE" if expression.args.get("purge") else "" 1490 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1491 1492 def set_operation(self, expression: exp.SetOperation) -> str: 1493 op_type = type(expression) 1494 op_name = op_type.key.upper() 1495 1496 distinct = expression.args.get("distinct") 1497 if ( 1498 distinct is False 1499 and op_type in (exp.Except, exp.Intersect) 1500 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1501 ): 1502 self.unsupported(f"{op_name} ALL is not supported") 1503 1504 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1505 1506 if distinct is None: 1507 distinct = default_distinct 1508 if distinct is None: 1509 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1510 1511 if distinct is default_distinct: 1512 distinct_or_all = "" 1513 else: 1514 distinct_or_all = " DISTINCT" if distinct else " ALL" 1515 1516 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1517 side_kind = f"{side_kind} " if side_kind else "" 1518 1519 by_name = " BY NAME" if expression.args.get("by_name") else "" 1520 on = self.expressions(expression, key="on", flat=True) 1521 on = f" ON ({on})" if on else "" 1522 1523 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1524 1525 def set_operations(self, expression: exp.SetOperation) -> str: 1526 if not self.SET_OP_MODIFIERS: 1527 limit = expression.args.get("limit") 1528 order = expression.args.get("order") 1529 1530 if limit or order: 1531 select = self._move_ctes_to_top_level( 1532 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1533 ) 1534 1535 if limit: 1536 select = select.limit(limit.pop(), copy=False) 1537 if order: 1538 select = select.order_by(order.pop(), copy=False) 1539 return self.sql(select) 1540 1541 sqls: t.List[str] = [] 1542 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1543 1544 while stack: 1545 node = stack.pop() 1546 1547 if isinstance(node, exp.SetOperation): 1548 stack.append(node.expression) 1549 stack.append( 1550 self.maybe_comment( 1551 self.set_operation(node), comments=node.comments, separated=True 1552 ) 1553 ) 1554 stack.append(node.this) 1555 else: 1556 sqls.append(self.sql(node)) 1557 1558 this = self.sep().join(sqls) 1559 this = self.query_modifiers(expression, this) 1560 return self.prepend_ctes(expression, this) 1561 1562 def fetch_sql(self, expression: exp.Fetch) -> str: 1563 direction = expression.args.get("direction") 1564 direction = f" {direction}" if direction else "" 1565 count = self.sql(expression, "count") 1566 count = f" {count}" if count else "" 1567 limit_options = self.sql(expression, "limit_options") 1568 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1569 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1570 1571 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1572 percent = " PERCENT" if expression.args.get("percent") else "" 1573 rows = " ROWS" if expression.args.get("rows") else "" 1574 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1575 if not with_ties and rows: 1576 with_ties = " ONLY" 1577 return f"{percent}{rows}{with_ties}" 1578 1579 def filter_sql(self, expression: exp.Filter) -> str: 1580 if self.AGGREGATE_FILTER_SUPPORTED: 1581 this = self.sql(expression, "this") 1582 where = self.sql(expression, "expression").strip() 1583 return f"{this} FILTER({where})" 1584 1585 agg = expression.this 1586 agg_arg = agg.this 1587 cond = expression.expression.this 1588 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1589 return self.sql(agg) 1590 1591 def hint_sql(self, expression: exp.Hint) -> str: 1592 if not self.QUERY_HINTS: 1593 self.unsupported("Hints are not supported") 1594 return "" 1595 1596 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1597 1598 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1599 using = self.sql(expression, "using") 1600 using = f" USING {using}" if using else "" 1601 columns = self.expressions(expression, key="columns", flat=True) 1602 columns = f"({columns})" if columns else "" 1603 partition_by = self.expressions(expression, key="partition_by", flat=True) 1604 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1605 where = self.sql(expression, "where") 1606 include = self.expressions(expression, key="include", flat=True) 1607 if include: 1608 include = f" INCLUDE ({include})" 1609 with_storage = self.expressions(expression, key="with_storage", flat=True) 1610 with_storage = f" WITH ({with_storage})" if with_storage else "" 1611 tablespace = self.sql(expression, "tablespace") 1612 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1613 on = self.sql(expression, "on") 1614 on = f" ON {on}" if on else "" 1615 1616 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1617 1618 def index_sql(self, expression: exp.Index) -> str: 1619 unique = "UNIQUE " if expression.args.get("unique") else "" 1620 primary = "PRIMARY " if expression.args.get("primary") else "" 1621 amp = "AMP " if expression.args.get("amp") else "" 1622 name = self.sql(expression, "this") 1623 name = f"{name} " if name else "" 1624 table = self.sql(expression, "table") 1625 table = f"{self.INDEX_ON} {table}" if table else "" 1626 1627 index = "INDEX " if not table else "" 1628 1629 params = self.sql(expression, "params") 1630 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1631 1632 def identifier_sql(self, expression: exp.Identifier) -> str: 1633 text = expression.name 1634 lower = text.lower() 1635 text = lower if self.normalize and not expression.quoted else text 1636 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1637 if ( 1638 expression.quoted 1639 or self.dialect.can_identify(text, self.identify) 1640 or lower in self.RESERVED_KEYWORDS 1641 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1642 ): 1643 text = f"{self._identifier_start}{text}{self._identifier_end}" 1644 return text 1645 1646 def hex_sql(self, expression: exp.Hex) -> str: 1647 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1648 if self.dialect.HEX_LOWERCASE: 1649 text = self.func("LOWER", text) 1650 1651 return text 1652 1653 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1654 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1655 if not self.dialect.HEX_LOWERCASE: 1656 text = self.func("LOWER", text) 1657 return text 1658 1659 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1660 input_format = self.sql(expression, "input_format") 1661 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1662 output_format = self.sql(expression, "output_format") 1663 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1664 return self.sep().join((input_format, output_format)) 1665 1666 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1667 string = self.sql(exp.Literal.string(expression.name)) 1668 return f"{prefix}{string}" 1669 1670 def partition_sql(self, expression: exp.Partition) -> str: 1671 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1672 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1673 1674 def properties_sql(self, expression: exp.Properties) -> str: 1675 root_properties = [] 1676 with_properties = [] 1677 1678 for p in expression.expressions: 1679 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1680 if p_loc == exp.Properties.Location.POST_WITH: 1681 with_properties.append(p) 1682 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1683 root_properties.append(p) 1684 1685 root_props_ast = exp.Properties(expressions=root_properties) 1686 root_props_ast.parent = expression.parent 1687 1688 with_props_ast = exp.Properties(expressions=with_properties) 1689 with_props_ast.parent = expression.parent 1690 1691 root_props = self.root_properties(root_props_ast) 1692 with_props = self.with_properties(with_props_ast) 1693 1694 if root_props and with_props and not self.pretty: 1695 with_props = " " + with_props 1696 1697 return root_props + with_props 1698 1699 def root_properties(self, properties: exp.Properties) -> str: 1700 if properties.expressions: 1701 return self.expressions(properties, indent=False, sep=" ") 1702 return "" 1703 1704 def properties( 1705 self, 1706 properties: exp.Properties, 1707 prefix: str = "", 1708 sep: str = ", ", 1709 suffix: str = "", 1710 wrapped: bool = True, 1711 ) -> str: 1712 if properties.expressions: 1713 expressions = self.expressions(properties, sep=sep, indent=False) 1714 if expressions: 1715 expressions = self.wrap(expressions) if wrapped else expressions 1716 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1717 return "" 1718 1719 def with_properties(self, properties: exp.Properties) -> str: 1720 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1721 1722 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1723 properties_locs = defaultdict(list) 1724 for p in properties.expressions: 1725 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1726 if p_loc != exp.Properties.Location.UNSUPPORTED: 1727 properties_locs[p_loc].append(p) 1728 else: 1729 self.unsupported(f"Unsupported property {p.key}") 1730 1731 return properties_locs 1732 1733 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1734 if isinstance(expression.this, exp.Dot): 1735 return self.sql(expression, "this") 1736 return f"'{expression.name}'" if string_key else expression.name 1737 1738 def property_sql(self, expression: exp.Property) -> str: 1739 property_cls = expression.__class__ 1740 if property_cls == exp.Property: 1741 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1742 1743 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1744 if not property_name: 1745 self.unsupported(f"Unsupported property {expression.key}") 1746 1747 return f"{property_name}={self.sql(expression, 'this')}" 1748 1749 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1750 if self.SUPPORTS_CREATE_TABLE_LIKE: 1751 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1752 options = f" {options}" if options else "" 1753 1754 like = f"LIKE {self.sql(expression, 'this')}{options}" 1755 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1756 like = f"({like})" 1757 1758 return like 1759 1760 if expression.expressions: 1761 self.unsupported("Transpilation of LIKE property options is unsupported") 1762 1763 select = exp.select("*").from_(expression.this).limit(0) 1764 return f"AS {self.sql(select)}" 1765 1766 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1767 no = "NO " if expression.args.get("no") else "" 1768 protection = " PROTECTION" if expression.args.get("protection") else "" 1769 return f"{no}FALLBACK{protection}" 1770 1771 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1772 no = "NO " if expression.args.get("no") else "" 1773 local = expression.args.get("local") 1774 local = f"{local} " if local else "" 1775 dual = "DUAL " if expression.args.get("dual") else "" 1776 before = "BEFORE " if expression.args.get("before") else "" 1777 after = "AFTER " if expression.args.get("after") else "" 1778 return f"{no}{local}{dual}{before}{after}JOURNAL" 1779 1780 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1781 freespace = self.sql(expression, "this") 1782 percent = " PERCENT" if expression.args.get("percent") else "" 1783 return f"FREESPACE={freespace}{percent}" 1784 1785 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1786 if expression.args.get("default"): 1787 property = "DEFAULT" 1788 elif expression.args.get("on"): 1789 property = "ON" 1790 else: 1791 property = "OFF" 1792 return f"CHECKSUM={property}" 1793 1794 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1795 if expression.args.get("no"): 1796 return "NO MERGEBLOCKRATIO" 1797 if expression.args.get("default"): 1798 return "DEFAULT MERGEBLOCKRATIO" 1799 1800 percent = " PERCENT" if expression.args.get("percent") else "" 1801 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1802 1803 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1804 default = expression.args.get("default") 1805 minimum = expression.args.get("minimum") 1806 maximum = expression.args.get("maximum") 1807 if default or minimum or maximum: 1808 if default: 1809 prop = "DEFAULT" 1810 elif minimum: 1811 prop = "MINIMUM" 1812 else: 1813 prop = "MAXIMUM" 1814 return f"{prop} DATABLOCKSIZE" 1815 units = expression.args.get("units") 1816 units = f" {units}" if units else "" 1817 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1818 1819 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1820 autotemp = expression.args.get("autotemp") 1821 always = expression.args.get("always") 1822 default = expression.args.get("default") 1823 manual = expression.args.get("manual") 1824 never = expression.args.get("never") 1825 1826 if autotemp is not None: 1827 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1828 elif always: 1829 prop = "ALWAYS" 1830 elif default: 1831 prop = "DEFAULT" 1832 elif manual: 1833 prop = "MANUAL" 1834 elif never: 1835 prop = "NEVER" 1836 return f"BLOCKCOMPRESSION={prop}" 1837 1838 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1839 no = expression.args.get("no") 1840 no = " NO" if no else "" 1841 concurrent = expression.args.get("concurrent") 1842 concurrent = " CONCURRENT" if concurrent else "" 1843 target = self.sql(expression, "target") 1844 target = f" {target}" if target else "" 1845 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1846 1847 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1848 if isinstance(expression.this, list): 1849 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1850 if expression.this: 1851 modulus = self.sql(expression, "this") 1852 remainder = self.sql(expression, "expression") 1853 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1854 1855 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1856 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1857 return f"FROM ({from_expressions}) TO ({to_expressions})" 1858 1859 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1860 this = self.sql(expression, "this") 1861 1862 for_values_or_default = expression.expression 1863 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1864 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1865 else: 1866 for_values_or_default = " DEFAULT" 1867 1868 return f"PARTITION OF {this}{for_values_or_default}" 1869 1870 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1871 kind = expression.args.get("kind") 1872 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1873 for_or_in = expression.args.get("for_or_in") 1874 for_or_in = f" {for_or_in}" if for_or_in else "" 1875 lock_type = expression.args.get("lock_type") 1876 override = " OVERRIDE" if expression.args.get("override") else "" 1877 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1878 1879 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1880 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1881 statistics = expression.args.get("statistics") 1882 statistics_sql = "" 1883 if statistics is not None: 1884 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1885 return f"{data_sql}{statistics_sql}" 1886 1887 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1888 this = self.sql(expression, "this") 1889 this = f"HISTORY_TABLE={this}" if this else "" 1890 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1891 data_consistency = ( 1892 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1893 ) 1894 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1895 retention_period = ( 1896 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1897 ) 1898 1899 if this: 1900 on_sql = self.func("ON", this, data_consistency, retention_period) 1901 else: 1902 on_sql = "ON" if expression.args.get("on") else "OFF" 1903 1904 sql = f"SYSTEM_VERSIONING={on_sql}" 1905 1906 return f"WITH({sql})" if expression.args.get("with") else sql 1907 1908 def insert_sql(self, expression: exp.Insert) -> str: 1909 hint = self.sql(expression, "hint") 1910 overwrite = expression.args.get("overwrite") 1911 1912 if isinstance(expression.this, exp.Directory): 1913 this = " OVERWRITE" if overwrite else " INTO" 1914 else: 1915 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1916 1917 stored = self.sql(expression, "stored") 1918 stored = f" {stored}" if stored else "" 1919 alternative = expression.args.get("alternative") 1920 alternative = f" OR {alternative}" if alternative else "" 1921 ignore = " IGNORE" if expression.args.get("ignore") else "" 1922 is_function = expression.args.get("is_function") 1923 if is_function: 1924 this = f"{this} FUNCTION" 1925 this = f"{this} {self.sql(expression, 'this')}" 1926 1927 exists = " IF EXISTS" if expression.args.get("exists") else "" 1928 where = self.sql(expression, "where") 1929 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1930 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1931 on_conflict = self.sql(expression, "conflict") 1932 on_conflict = f" {on_conflict}" if on_conflict else "" 1933 by_name = " BY NAME" if expression.args.get("by_name") else "" 1934 returning = self.sql(expression, "returning") 1935 1936 if self.RETURNING_END: 1937 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1938 else: 1939 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1940 1941 partition_by = self.sql(expression, "partition") 1942 partition_by = f" {partition_by}" if partition_by else "" 1943 settings = self.sql(expression, "settings") 1944 settings = f" {settings}" if settings else "" 1945 1946 source = self.sql(expression, "source") 1947 source = f"TABLE {source}" if source else "" 1948 1949 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1950 return self.prepend_ctes(expression, sql) 1951 1952 def introducer_sql(self, expression: exp.Introducer) -> str: 1953 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1954 1955 def kill_sql(self, expression: exp.Kill) -> str: 1956 kind = self.sql(expression, "kind") 1957 kind = f" {kind}" if kind else "" 1958 this = self.sql(expression, "this") 1959 this = f" {this}" if this else "" 1960 return f"KILL{kind}{this}" 1961 1962 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1963 return expression.name 1964 1965 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1966 return expression.name 1967 1968 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1969 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1970 1971 constraint = self.sql(expression, "constraint") 1972 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1973 1974 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1975 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1976 action = self.sql(expression, "action") 1977 1978 expressions = self.expressions(expression, flat=True) 1979 if expressions: 1980 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1981 expressions = f" {set_keyword}{expressions}" 1982 1983 where = self.sql(expression, "where") 1984 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1985 1986 def returning_sql(self, expression: exp.Returning) -> str: 1987 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1988 1989 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1990 fields = self.sql(expression, "fields") 1991 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1992 escaped = self.sql(expression, "escaped") 1993 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1994 items = self.sql(expression, "collection_items") 1995 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1996 keys = self.sql(expression, "map_keys") 1997 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1998 lines = self.sql(expression, "lines") 1999 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2000 null = self.sql(expression, "null") 2001 null = f" NULL DEFINED AS {null}" if null else "" 2002 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2003 2004 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2005 return f"WITH ({self.expressions(expression, flat=True)})" 2006 2007 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2008 this = f"{self.sql(expression, 'this')} INDEX" 2009 target = self.sql(expression, "target") 2010 target = f" FOR {target}" if target else "" 2011 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2012 2013 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2014 this = self.sql(expression, "this") 2015 kind = self.sql(expression, "kind") 2016 expr = self.sql(expression, "expression") 2017 return f"{this} ({kind} => {expr})" 2018 2019 def table_parts(self, expression: exp.Table) -> str: 2020 return ".".join( 2021 self.sql(part) 2022 for part in ( 2023 expression.args.get("catalog"), 2024 expression.args.get("db"), 2025 expression.args.get("this"), 2026 ) 2027 if part is not None 2028 ) 2029 2030 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2031 table = self.table_parts(expression) 2032 only = "ONLY " if expression.args.get("only") else "" 2033 partition = self.sql(expression, "partition") 2034 partition = f" {partition}" if partition else "" 2035 version = self.sql(expression, "version") 2036 version = f" {version}" if version else "" 2037 alias = self.sql(expression, "alias") 2038 alias = f"{sep}{alias}" if alias else "" 2039 2040 sample = self.sql(expression, "sample") 2041 if self.dialect.ALIAS_POST_TABLESAMPLE: 2042 sample_pre_alias = sample 2043 sample_post_alias = "" 2044 else: 2045 sample_pre_alias = "" 2046 sample_post_alias = sample 2047 2048 hints = self.expressions(expression, key="hints", sep=" ") 2049 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2050 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2051 joins = self.indent( 2052 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2053 ) 2054 laterals = self.expressions(expression, key="laterals", sep="") 2055 2056 file_format = self.sql(expression, "format") 2057 if file_format: 2058 pattern = self.sql(expression, "pattern") 2059 pattern = f", PATTERN => {pattern}" if pattern else "" 2060 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2061 2062 ordinality = expression.args.get("ordinality") or "" 2063 if ordinality: 2064 ordinality = f" WITH ORDINALITY{alias}" 2065 alias = "" 2066 2067 when = self.sql(expression, "when") 2068 if when: 2069 table = f"{table} {when}" 2070 2071 changes = self.sql(expression, "changes") 2072 changes = f" {changes}" if changes else "" 2073 2074 rows_from = self.expressions(expression, key="rows_from") 2075 if rows_from: 2076 table = f"ROWS FROM {self.wrap(rows_from)}" 2077 2078 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2079 2080 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2081 table = self.func("TABLE", expression.this) 2082 alias = self.sql(expression, "alias") 2083 alias = f" AS {alias}" if alias else "" 2084 sample = self.sql(expression, "sample") 2085 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2086 joins = self.indent( 2087 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2088 ) 2089 return f"{table}{alias}{pivots}{sample}{joins}" 2090 2091 def tablesample_sql( 2092 self, 2093 expression: exp.TableSample, 2094 tablesample_keyword: t.Optional[str] = None, 2095 ) -> str: 2096 method = self.sql(expression, "method") 2097 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2098 numerator = self.sql(expression, "bucket_numerator") 2099 denominator = self.sql(expression, "bucket_denominator") 2100 field = self.sql(expression, "bucket_field") 2101 field = f" ON {field}" if field else "" 2102 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2103 seed = self.sql(expression, "seed") 2104 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2105 2106 size = self.sql(expression, "size") 2107 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2108 size = f"{size} ROWS" 2109 2110 percent = self.sql(expression, "percent") 2111 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2112 percent = f"{percent} PERCENT" 2113 2114 expr = f"{bucket}{percent}{size}" 2115 if self.TABLESAMPLE_REQUIRES_PARENS: 2116 expr = f"({expr})" 2117 2118 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2119 2120 def pivot_sql(self, expression: exp.Pivot) -> str: 2121 expressions = self.expressions(expression, flat=True) 2122 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2123 2124 group = self.sql(expression, "group") 2125 2126 if expression.this: 2127 this = self.sql(expression, "this") 2128 if not expressions: 2129 return f"UNPIVOT {this}" 2130 2131 on = f"{self.seg('ON')} {expressions}" 2132 into = self.sql(expression, "into") 2133 into = f"{self.seg('INTO')} {into}" if into else "" 2134 using = self.expressions(expression, key="using", flat=True) 2135 using = f"{self.seg('USING')} {using}" if using else "" 2136 return f"{direction} {this}{on}{into}{using}{group}" 2137 2138 alias = self.sql(expression, "alias") 2139 alias = f" AS {alias}" if alias else "" 2140 2141 fields = self.expressions( 2142 expression, 2143 "fields", 2144 sep=" ", 2145 dynamic=True, 2146 new_line=True, 2147 skip_first=True, 2148 skip_last=True, 2149 ) 2150 2151 include_nulls = expression.args.get("include_nulls") 2152 if include_nulls is not None: 2153 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2154 else: 2155 nulls = "" 2156 2157 default_on_null = self.sql(expression, "default_on_null") 2158 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2159 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2160 2161 def version_sql(self, expression: exp.Version) -> str: 2162 this = f"FOR {expression.name}" 2163 kind = expression.text("kind") 2164 expr = self.sql(expression, "expression") 2165 return f"{this} {kind} {expr}" 2166 2167 def tuple_sql(self, expression: exp.Tuple) -> str: 2168 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2169 2170 def update_sql(self, expression: exp.Update) -> str: 2171 this = self.sql(expression, "this") 2172 set_sql = self.expressions(expression, flat=True) 2173 from_sql = self.sql(expression, "from") 2174 where_sql = self.sql(expression, "where") 2175 returning = self.sql(expression, "returning") 2176 order = self.sql(expression, "order") 2177 limit = self.sql(expression, "limit") 2178 if self.RETURNING_END: 2179 expression_sql = f"{from_sql}{where_sql}{returning}" 2180 else: 2181 expression_sql = f"{returning}{from_sql}{where_sql}" 2182 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2183 return self.prepend_ctes(expression, sql) 2184 2185 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2186 values_as_table = values_as_table and self.VALUES_AS_TABLE 2187 2188 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2189 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2190 args = self.expressions(expression) 2191 alias = self.sql(expression, "alias") 2192 values = f"VALUES{self.seg('')}{args}" 2193 values = ( 2194 f"({values})" 2195 if self.WRAP_DERIVED_VALUES 2196 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2197 else values 2198 ) 2199 return f"{values} AS {alias}" if alias else values 2200 2201 # Converts `VALUES...` expression into a series of select unions. 2202 alias_node = expression.args.get("alias") 2203 column_names = alias_node and alias_node.columns 2204 2205 selects: t.List[exp.Query] = [] 2206 2207 for i, tup in enumerate(expression.expressions): 2208 row = tup.expressions 2209 2210 if i == 0 and column_names: 2211 row = [ 2212 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2213 ] 2214 2215 selects.append(exp.Select(expressions=row)) 2216 2217 if self.pretty: 2218 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2219 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2220 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2221 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2222 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2223 2224 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2225 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2226 return f"({unions}){alias}" 2227 2228 def var_sql(self, expression: exp.Var) -> str: 2229 return self.sql(expression, "this") 2230 2231 @unsupported_args("expressions") 2232 def into_sql(self, expression: exp.Into) -> str: 2233 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2234 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2235 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2236 2237 def from_sql(self, expression: exp.From) -> str: 2238 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2239 2240 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2241 grouping_sets = self.expressions(expression, indent=False) 2242 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2243 2244 def rollup_sql(self, expression: exp.Rollup) -> str: 2245 expressions = self.expressions(expression, indent=False) 2246 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2247 2248 def cube_sql(self, expression: exp.Cube) -> str: 2249 expressions = self.expressions(expression, indent=False) 2250 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2251 2252 def group_sql(self, expression: exp.Group) -> str: 2253 group_by_all = expression.args.get("all") 2254 if group_by_all is True: 2255 modifier = " ALL" 2256 elif group_by_all is False: 2257 modifier = " DISTINCT" 2258 else: 2259 modifier = "" 2260 2261 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2262 2263 grouping_sets = self.expressions(expression, key="grouping_sets") 2264 cube = self.expressions(expression, key="cube") 2265 rollup = self.expressions(expression, key="rollup") 2266 2267 groupings = csv( 2268 self.seg(grouping_sets) if grouping_sets else "", 2269 self.seg(cube) if cube else "", 2270 self.seg(rollup) if rollup else "", 2271 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2272 sep=self.GROUPINGS_SEP, 2273 ) 2274 2275 if ( 2276 expression.expressions 2277 and groupings 2278 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2279 ): 2280 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2281 2282 return f"{group_by}{groupings}" 2283 2284 def having_sql(self, expression: exp.Having) -> str: 2285 this = self.indent(self.sql(expression, "this")) 2286 return f"{self.seg('HAVING')}{self.sep()}{this}" 2287 2288 def connect_sql(self, expression: exp.Connect) -> str: 2289 start = self.sql(expression, "start") 2290 start = self.seg(f"START WITH {start}") if start else "" 2291 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2292 connect = self.sql(expression, "connect") 2293 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2294 return start + connect 2295 2296 def prior_sql(self, expression: exp.Prior) -> str: 2297 return f"PRIOR {self.sql(expression, 'this')}" 2298 2299 def join_sql(self, expression: exp.Join) -> str: 2300 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2301 side = None 2302 else: 2303 side = expression.side 2304 2305 op_sql = " ".join( 2306 op 2307 for op in ( 2308 expression.method, 2309 "GLOBAL" if expression.args.get("global") else None, 2310 side, 2311 expression.kind, 2312 expression.hint if self.JOIN_HINTS else None, 2313 ) 2314 if op 2315 ) 2316 match_cond = self.sql(expression, "match_condition") 2317 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2318 on_sql = self.sql(expression, "on") 2319 using = expression.args.get("using") 2320 2321 if not on_sql and using: 2322 on_sql = csv(*(self.sql(column) for column in using)) 2323 2324 this = expression.this 2325 this_sql = self.sql(this) 2326 2327 exprs = self.expressions(expression) 2328 if exprs: 2329 this_sql = f"{this_sql},{self.seg(exprs)}" 2330 2331 if on_sql: 2332 on_sql = self.indent(on_sql, skip_first=True) 2333 space = self.seg(" " * self.pad) if self.pretty else " " 2334 if using: 2335 on_sql = f"{space}USING ({on_sql})" 2336 else: 2337 on_sql = f"{space}ON {on_sql}" 2338 elif not op_sql: 2339 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2340 return f" {this_sql}" 2341 2342 return f", {this_sql}" 2343 2344 if op_sql != "STRAIGHT_JOIN": 2345 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2346 2347 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2348 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2349 2350 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2351 args = self.expressions(expression, flat=True) 2352 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2353 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2354 2355 def lateral_op(self, expression: exp.Lateral) -> str: 2356 cross_apply = expression.args.get("cross_apply") 2357 2358 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2359 if cross_apply is True: 2360 op = "INNER JOIN " 2361 elif cross_apply is False: 2362 op = "LEFT JOIN " 2363 else: 2364 op = "" 2365 2366 return f"{op}LATERAL" 2367 2368 def lateral_sql(self, expression: exp.Lateral) -> str: 2369 this = self.sql(expression, "this") 2370 2371 if expression.args.get("view"): 2372 alias = expression.args["alias"] 2373 columns = self.expressions(alias, key="columns", flat=True) 2374 table = f" {alias.name}" if alias.name else "" 2375 columns = f" AS {columns}" if columns else "" 2376 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2377 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2378 2379 alias = self.sql(expression, "alias") 2380 alias = f" AS {alias}" if alias else "" 2381 2382 ordinality = expression.args.get("ordinality") or "" 2383 if ordinality: 2384 ordinality = f" WITH ORDINALITY{alias}" 2385 alias = "" 2386 2387 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2388 2389 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2390 this = self.sql(expression, "this") 2391 2392 args = [ 2393 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2394 for e in (expression.args.get(k) for k in ("offset", "expression")) 2395 if e 2396 ] 2397 2398 args_sql = ", ".join(self.sql(e) for e in args) 2399 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2400 expressions = self.expressions(expression, flat=True) 2401 limit_options = self.sql(expression, "limit_options") 2402 expressions = f" BY {expressions}" if expressions else "" 2403 2404 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2405 2406 def offset_sql(self, expression: exp.Offset) -> str: 2407 this = self.sql(expression, "this") 2408 value = expression.expression 2409 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2410 expressions = self.expressions(expression, flat=True) 2411 expressions = f" BY {expressions}" if expressions else "" 2412 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2413 2414 def setitem_sql(self, expression: exp.SetItem) -> str: 2415 kind = self.sql(expression, "kind") 2416 kind = f"{kind} " if kind else "" 2417 this = self.sql(expression, "this") 2418 expressions = self.expressions(expression) 2419 collate = self.sql(expression, "collate") 2420 collate = f" COLLATE {collate}" if collate else "" 2421 global_ = "GLOBAL " if expression.args.get("global") else "" 2422 return f"{global_}{kind}{this}{expressions}{collate}" 2423 2424 def set_sql(self, expression: exp.Set) -> str: 2425 expressions = f" {self.expressions(expression, flat=True)}" 2426 tag = " TAG" if expression.args.get("tag") else "" 2427 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2428 2429 def queryband_sql(self, expression: exp.QueryBand) -> str: 2430 this = self.sql(expression, "this") 2431 update = " UPDATE" if expression.args.get("update") else "" 2432 scope = self.sql(expression, "scope") 2433 scope = f" FOR {scope}" if scope else "" 2434 2435 return f"QUERY_BAND = {this}{update}{scope}" 2436 2437 def pragma_sql(self, expression: exp.Pragma) -> str: 2438 return f"PRAGMA {self.sql(expression, 'this')}" 2439 2440 def lock_sql(self, expression: exp.Lock) -> str: 2441 if not self.LOCKING_READS_SUPPORTED: 2442 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2443 return "" 2444 2445 update = expression.args["update"] 2446 key = expression.args.get("key") 2447 if update: 2448 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2449 else: 2450 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2451 expressions = self.expressions(expression, flat=True) 2452 expressions = f" OF {expressions}" if expressions else "" 2453 wait = expression.args.get("wait") 2454 2455 if wait is not None: 2456 if isinstance(wait, exp.Literal): 2457 wait = f" WAIT {self.sql(wait)}" 2458 else: 2459 wait = " NOWAIT" if wait else " SKIP LOCKED" 2460 2461 return f"{lock_type}{expressions}{wait or ''}" 2462 2463 def literal_sql(self, expression: exp.Literal) -> str: 2464 text = expression.this or "" 2465 if expression.is_string: 2466 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2467 return text 2468 2469 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2470 if self.dialect.ESCAPED_SEQUENCES: 2471 to_escaped = self.dialect.ESCAPED_SEQUENCES 2472 text = "".join( 2473 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2474 ) 2475 2476 return self._replace_line_breaks(text).replace( 2477 self.dialect.QUOTE_END, self._escaped_quote_end 2478 ) 2479 2480 def loaddata_sql(self, expression: exp.LoadData) -> str: 2481 local = " LOCAL" if expression.args.get("local") else "" 2482 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2483 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2484 this = f" INTO TABLE {self.sql(expression, 'this')}" 2485 partition = self.sql(expression, "partition") 2486 partition = f" {partition}" if partition else "" 2487 input_format = self.sql(expression, "input_format") 2488 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2489 serde = self.sql(expression, "serde") 2490 serde = f" SERDE {serde}" if serde else "" 2491 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2492 2493 def null_sql(self, *_) -> str: 2494 return "NULL" 2495 2496 def boolean_sql(self, expression: exp.Boolean) -> str: 2497 return "TRUE" if expression.this else "FALSE" 2498 2499 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2500 this = self.sql(expression, "this") 2501 this = f"{this} " if this else this 2502 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2503 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2504 2505 def withfill_sql(self, expression: exp.WithFill) -> str: 2506 from_sql = self.sql(expression, "from") 2507 from_sql = f" FROM {from_sql}" if from_sql else "" 2508 to_sql = self.sql(expression, "to") 2509 to_sql = f" TO {to_sql}" if to_sql else "" 2510 step_sql = self.sql(expression, "step") 2511 step_sql = f" STEP {step_sql}" if step_sql else "" 2512 interpolated_values = [ 2513 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2514 if isinstance(e, exp.Alias) 2515 else self.sql(e, "this") 2516 for e in expression.args.get("interpolate") or [] 2517 ] 2518 interpolate = ( 2519 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2520 ) 2521 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2522 2523 def cluster_sql(self, expression: exp.Cluster) -> str: 2524 return self.op_expressions("CLUSTER BY", expression) 2525 2526 def distribute_sql(self, expression: exp.Distribute) -> str: 2527 return self.op_expressions("DISTRIBUTE BY", expression) 2528 2529 def sort_sql(self, expression: exp.Sort) -> str: 2530 return self.op_expressions("SORT BY", expression) 2531 2532 def ordered_sql(self, expression: exp.Ordered) -> str: 2533 desc = expression.args.get("desc") 2534 asc = not desc 2535 2536 nulls_first = expression.args.get("nulls_first") 2537 nulls_last = not nulls_first 2538 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2539 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2540 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2541 2542 this = self.sql(expression, "this") 2543 2544 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2545 nulls_sort_change = "" 2546 if nulls_first and ( 2547 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2548 ): 2549 nulls_sort_change = " NULLS FIRST" 2550 elif ( 2551 nulls_last 2552 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2553 and not nulls_are_last 2554 ): 2555 nulls_sort_change = " NULLS LAST" 2556 2557 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2558 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2559 window = expression.find_ancestor(exp.Window, exp.Select) 2560 if isinstance(window, exp.Window) and window.args.get("spec"): 2561 self.unsupported( 2562 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2563 ) 2564 nulls_sort_change = "" 2565 elif self.NULL_ORDERING_SUPPORTED is False and ( 2566 (asc and nulls_sort_change == " NULLS LAST") 2567 or (desc and nulls_sort_change == " NULLS FIRST") 2568 ): 2569 # BigQuery does not allow these ordering/nulls combinations when used under 2570 # an aggregation func or under a window containing one 2571 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2572 2573 if isinstance(ancestor, exp.Window): 2574 ancestor = ancestor.this 2575 if isinstance(ancestor, exp.AggFunc): 2576 self.unsupported( 2577 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2578 ) 2579 nulls_sort_change = "" 2580 elif self.NULL_ORDERING_SUPPORTED is None: 2581 if expression.this.is_int: 2582 self.unsupported( 2583 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2584 ) 2585 elif not isinstance(expression.this, exp.Rand): 2586 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2587 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2588 nulls_sort_change = "" 2589 2590 with_fill = self.sql(expression, "with_fill") 2591 with_fill = f" {with_fill}" if with_fill else "" 2592 2593 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2594 2595 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2596 window_frame = self.sql(expression, "window_frame") 2597 window_frame = f"{window_frame} " if window_frame else "" 2598 2599 this = self.sql(expression, "this") 2600 2601 return f"{window_frame}{this}" 2602 2603 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2604 partition = self.partition_by_sql(expression) 2605 order = self.sql(expression, "order") 2606 measures = self.expressions(expression, key="measures") 2607 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2608 rows = self.sql(expression, "rows") 2609 rows = self.seg(rows) if rows else "" 2610 after = self.sql(expression, "after") 2611 after = self.seg(after) if after else "" 2612 pattern = self.sql(expression, "pattern") 2613 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2614 definition_sqls = [ 2615 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2616 for definition in expression.args.get("define", []) 2617 ] 2618 definitions = self.expressions(sqls=definition_sqls) 2619 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2620 body = "".join( 2621 ( 2622 partition, 2623 order, 2624 measures, 2625 rows, 2626 after, 2627 pattern, 2628 define, 2629 ) 2630 ) 2631 alias = self.sql(expression, "alias") 2632 alias = f" {alias}" if alias else "" 2633 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2634 2635 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2636 limit = expression.args.get("limit") 2637 2638 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2639 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2640 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2641 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2642 2643 return csv( 2644 *sqls, 2645 *[self.sql(join) for join in expression.args.get("joins") or []], 2646 self.sql(expression, "match"), 2647 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2648 self.sql(expression, "prewhere"), 2649 self.sql(expression, "where"), 2650 self.sql(expression, "connect"), 2651 self.sql(expression, "group"), 2652 self.sql(expression, "having"), 2653 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2654 self.sql(expression, "order"), 2655 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2656 *self.after_limit_modifiers(expression), 2657 self.options_modifier(expression), 2658 self.for_modifiers(expression), 2659 sep="", 2660 ) 2661 2662 def options_modifier(self, expression: exp.Expression) -> str: 2663 options = self.expressions(expression, key="options") 2664 return f" {options}" if options else "" 2665 2666 def for_modifiers(self, expression: exp.Expression) -> str: 2667 for_modifiers = self.expressions(expression, key="for") 2668 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2669 2670 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2671 self.unsupported("Unsupported query option.") 2672 return "" 2673 2674 def offset_limit_modifiers( 2675 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2676 ) -> t.List[str]: 2677 return [ 2678 self.sql(expression, "offset") if fetch else self.sql(limit), 2679 self.sql(limit) if fetch else self.sql(expression, "offset"), 2680 ] 2681 2682 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2683 locks = self.expressions(expression, key="locks", sep=" ") 2684 locks = f" {locks}" if locks else "" 2685 return [locks, self.sql(expression, "sample")] 2686 2687 def select_sql(self, expression: exp.Select) -> str: 2688 into = expression.args.get("into") 2689 if not self.SUPPORTS_SELECT_INTO and into: 2690 into.pop() 2691 2692 hint = self.sql(expression, "hint") 2693 distinct = self.sql(expression, "distinct") 2694 distinct = f" {distinct}" if distinct else "" 2695 kind = self.sql(expression, "kind") 2696 2697 limit = expression.args.get("limit") 2698 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2699 top = self.limit_sql(limit, top=True) 2700 limit.pop() 2701 else: 2702 top = "" 2703 2704 expressions = self.expressions(expression) 2705 2706 if kind: 2707 if kind in self.SELECT_KINDS: 2708 kind = f" AS {kind}" 2709 else: 2710 if kind == "STRUCT": 2711 expressions = self.expressions( 2712 sqls=[ 2713 self.sql( 2714 exp.Struct( 2715 expressions=[ 2716 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2717 if isinstance(e, exp.Alias) 2718 else e 2719 for e in expression.expressions 2720 ] 2721 ) 2722 ) 2723 ] 2724 ) 2725 kind = "" 2726 2727 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2728 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2729 2730 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2731 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2732 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2733 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2734 sql = self.query_modifiers( 2735 expression, 2736 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2737 self.sql(expression, "into", comment=False), 2738 self.sql(expression, "from", comment=False), 2739 ) 2740 2741 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2742 if expression.args.get("with"): 2743 sql = self.maybe_comment(sql, expression) 2744 expression.pop_comments() 2745 2746 sql = self.prepend_ctes(expression, sql) 2747 2748 if not self.SUPPORTS_SELECT_INTO and into: 2749 if into.args.get("temporary"): 2750 table_kind = " TEMPORARY" 2751 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2752 table_kind = " UNLOGGED" 2753 else: 2754 table_kind = "" 2755 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2756 2757 return sql 2758 2759 def schema_sql(self, expression: exp.Schema) -> str: 2760 this = self.sql(expression, "this") 2761 sql = self.schema_columns_sql(expression) 2762 return f"{this} {sql}" if this and sql else this or sql 2763 2764 def schema_columns_sql(self, expression: exp.Schema) -> str: 2765 if expression.expressions: 2766 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2767 return "" 2768 2769 def star_sql(self, expression: exp.Star) -> str: 2770 except_ = self.expressions(expression, key="except", flat=True) 2771 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2772 replace = self.expressions(expression, key="replace", flat=True) 2773 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2774 rename = self.expressions(expression, key="rename", flat=True) 2775 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2776 return f"*{except_}{replace}{rename}" 2777 2778 def parameter_sql(self, expression: exp.Parameter) -> str: 2779 this = self.sql(expression, "this") 2780 return f"{self.PARAMETER_TOKEN}{this}" 2781 2782 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2783 this = self.sql(expression, "this") 2784 kind = expression.text("kind") 2785 if kind: 2786 kind = f"{kind}." 2787 return f"@@{kind}{this}" 2788 2789 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2790 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2791 2792 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2793 alias = self.sql(expression, "alias") 2794 alias = f"{sep}{alias}" if alias else "" 2795 sample = self.sql(expression, "sample") 2796 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2797 alias = f"{sample}{alias}" 2798 2799 # Set to None so it's not generated again by self.query_modifiers() 2800 expression.set("sample", None) 2801 2802 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2803 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2804 return self.prepend_ctes(expression, sql) 2805 2806 def qualify_sql(self, expression: exp.Qualify) -> str: 2807 this = self.indent(self.sql(expression, "this")) 2808 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2809 2810 def unnest_sql(self, expression: exp.Unnest) -> str: 2811 args = self.expressions(expression, flat=True) 2812 2813 alias = expression.args.get("alias") 2814 offset = expression.args.get("offset") 2815 2816 if self.UNNEST_WITH_ORDINALITY: 2817 if alias and isinstance(offset, exp.Expression): 2818 alias.append("columns", offset) 2819 2820 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2821 columns = alias.columns 2822 alias = self.sql(columns[0]) if columns else "" 2823 else: 2824 alias = self.sql(alias) 2825 2826 alias = f" AS {alias}" if alias else alias 2827 if self.UNNEST_WITH_ORDINALITY: 2828 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2829 else: 2830 if isinstance(offset, exp.Expression): 2831 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2832 elif offset: 2833 suffix = f"{alias} WITH OFFSET" 2834 else: 2835 suffix = alias 2836 2837 return f"UNNEST({args}){suffix}" 2838 2839 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2840 return "" 2841 2842 def where_sql(self, expression: exp.Where) -> str: 2843 this = self.indent(self.sql(expression, "this")) 2844 return f"{self.seg('WHERE')}{self.sep()}{this}" 2845 2846 def window_sql(self, expression: exp.Window) -> str: 2847 this = self.sql(expression, "this") 2848 partition = self.partition_by_sql(expression) 2849 order = expression.args.get("order") 2850 order = self.order_sql(order, flat=True) if order else "" 2851 spec = self.sql(expression, "spec") 2852 alias = self.sql(expression, "alias") 2853 over = self.sql(expression, "over") or "OVER" 2854 2855 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2856 2857 first = expression.args.get("first") 2858 if first is None: 2859 first = "" 2860 else: 2861 first = "FIRST" if first else "LAST" 2862 2863 if not partition and not order and not spec and alias: 2864 return f"{this} {alias}" 2865 2866 args = self.format_args( 2867 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2868 ) 2869 return f"{this} ({args})" 2870 2871 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2872 partition = self.expressions(expression, key="partition_by", flat=True) 2873 return f"PARTITION BY {partition}" if partition else "" 2874 2875 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2876 kind = self.sql(expression, "kind") 2877 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2878 end = ( 2879 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2880 or "CURRENT ROW" 2881 ) 2882 2883 window_spec = f"{kind} BETWEEN {start} AND {end}" 2884 2885 exclude = self.sql(expression, "exclude") 2886 if exclude: 2887 if self.SUPPORTS_WINDOW_EXCLUDE: 2888 window_spec += f" EXCLUDE {exclude}" 2889 else: 2890 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2891 2892 return window_spec 2893 2894 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2895 this = self.sql(expression, "this") 2896 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2897 return f"{this} WITHIN GROUP ({expression_sql})" 2898 2899 def between_sql(self, expression: exp.Between) -> str: 2900 this = self.sql(expression, "this") 2901 low = self.sql(expression, "low") 2902 high = self.sql(expression, "high") 2903 symmetric = expression.args.get("symmetric") 2904 2905 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2906 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2907 2908 flag = ( 2909 " SYMMETRIC" 2910 if symmetric 2911 else " ASYMMETRIC" 2912 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2913 else "" # silently drop ASYMMETRIC – semantics identical 2914 ) 2915 return f"{this} BETWEEN{flag} {low} AND {high}" 2916 2917 def bracket_offset_expressions( 2918 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2919 ) -> t.List[exp.Expression]: 2920 return apply_index_offset( 2921 expression.this, 2922 expression.expressions, 2923 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2924 dialect=self.dialect, 2925 ) 2926 2927 def bracket_sql(self, expression: exp.Bracket) -> str: 2928 expressions = self.bracket_offset_expressions(expression) 2929 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2930 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2931 2932 def all_sql(self, expression: exp.All) -> str: 2933 this = self.sql(expression, "this") 2934 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2935 this = self.wrap(this) 2936 return f"ALL {this}" 2937 2938 def any_sql(self, expression: exp.Any) -> str: 2939 this = self.sql(expression, "this") 2940 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2941 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2942 this = self.wrap(this) 2943 return f"ANY{this}" 2944 return f"ANY {this}" 2945 2946 def exists_sql(self, expression: exp.Exists) -> str: 2947 return f"EXISTS{self.wrap(expression)}" 2948 2949 def case_sql(self, expression: exp.Case) -> str: 2950 this = self.sql(expression, "this") 2951 statements = [f"CASE {this}" if this else "CASE"] 2952 2953 for e in expression.args["ifs"]: 2954 statements.append(f"WHEN {self.sql(e, 'this')}") 2955 statements.append(f"THEN {self.sql(e, 'true')}") 2956 2957 default = self.sql(expression, "default") 2958 2959 if default: 2960 statements.append(f"ELSE {default}") 2961 2962 statements.append("END") 2963 2964 if self.pretty and self.too_wide(statements): 2965 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2966 2967 return " ".join(statements) 2968 2969 def constraint_sql(self, expression: exp.Constraint) -> str: 2970 this = self.sql(expression, "this") 2971 expressions = self.expressions(expression, flat=True) 2972 return f"CONSTRAINT {this} {expressions}" 2973 2974 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2975 order = expression.args.get("order") 2976 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2977 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2978 2979 def extract_sql(self, expression: exp.Extract) -> str: 2980 from sqlglot.dialects.dialect import map_date_part 2981 2982 this = ( 2983 map_date_part(expression.this, self.dialect) 2984 if self.NORMALIZE_EXTRACT_DATE_PARTS 2985 else expression.this 2986 ) 2987 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2988 expression_sql = self.sql(expression, "expression") 2989 2990 return f"EXTRACT({this_sql} FROM {expression_sql})" 2991 2992 def trim_sql(self, expression: exp.Trim) -> str: 2993 trim_type = self.sql(expression, "position") 2994 2995 if trim_type == "LEADING": 2996 func_name = "LTRIM" 2997 elif trim_type == "TRAILING": 2998 func_name = "RTRIM" 2999 else: 3000 func_name = "TRIM" 3001 3002 return self.func(func_name, expression.this, expression.expression) 3003 3004 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3005 args = expression.expressions 3006 if isinstance(expression, exp.ConcatWs): 3007 args = args[1:] # Skip the delimiter 3008 3009 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3010 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3011 3012 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3013 3014 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3015 if not e.type: 3016 from sqlglot.optimizer.annotate_types import annotate_types 3017 3018 e = annotate_types(e, dialect=self.dialect) 3019 3020 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3021 return e 3022 3023 return exp.func("coalesce", e, exp.Literal.string("")) 3024 3025 args = [_wrap_with_coalesce(e) for e in args] 3026 3027 return args 3028 3029 def concat_sql(self, expression: exp.Concat) -> str: 3030 expressions = self.convert_concat_args(expression) 3031 3032 # Some dialects don't allow a single-argument CONCAT call 3033 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3034 return self.sql(expressions[0]) 3035 3036 return self.func("CONCAT", *expressions) 3037 3038 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3039 return self.func( 3040 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3041 ) 3042 3043 def check_sql(self, expression: exp.Check) -> str: 3044 this = self.sql(expression, key="this") 3045 return f"CHECK ({this})" 3046 3047 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3048 expressions = self.expressions(expression, flat=True) 3049 expressions = f" ({expressions})" if expressions else "" 3050 reference = self.sql(expression, "reference") 3051 reference = f" {reference}" if reference else "" 3052 delete = self.sql(expression, "delete") 3053 delete = f" ON DELETE {delete}" if delete else "" 3054 update = self.sql(expression, "update") 3055 update = f" ON UPDATE {update}" if update else "" 3056 options = self.expressions(expression, key="options", flat=True, sep=" ") 3057 options = f" {options}" if options else "" 3058 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3059 3060 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3061 expressions = self.expressions(expression, flat=True) 3062 include = self.sql(expression, "include") 3063 options = self.expressions(expression, key="options", flat=True, sep=" ") 3064 options = f" {options}" if options else "" 3065 return f"PRIMARY KEY ({expressions}){include}{options}" 3066 3067 def if_sql(self, expression: exp.If) -> str: 3068 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3069 3070 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3071 modifier = expression.args.get("modifier") 3072 modifier = f" {modifier}" if modifier else "" 3073 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3074 3075 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3076 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3077 3078 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3079 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3080 3081 if expression.args.get("escape"): 3082 path = self.escape_str(path) 3083 3084 if self.QUOTE_JSON_PATH: 3085 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3086 3087 return path 3088 3089 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3090 if isinstance(expression, exp.JSONPathPart): 3091 transform = self.TRANSFORMS.get(expression.__class__) 3092 if not callable(transform): 3093 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3094 return "" 3095 3096 return transform(self, expression) 3097 3098 if isinstance(expression, int): 3099 return str(expression) 3100 3101 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3102 escaped = expression.replace("'", "\\'") 3103 escaped = f"\\'{expression}\\'" 3104 else: 3105 escaped = expression.replace('"', '\\"') 3106 escaped = f'"{escaped}"' 3107 3108 return escaped 3109 3110 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3111 return f"{self.sql(expression, 'this')} FORMAT JSON" 3112 3113 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3114 # Output the Teradata column FORMAT override. 3115 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3116 this = self.sql(expression, "this") 3117 fmt = self.sql(expression, "format") 3118 return f"{this} (FORMAT {fmt})" 3119 3120 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3121 null_handling = expression.args.get("null_handling") 3122 null_handling = f" {null_handling}" if null_handling else "" 3123 3124 unique_keys = expression.args.get("unique_keys") 3125 if unique_keys is not None: 3126 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3127 else: 3128 unique_keys = "" 3129 3130 return_type = self.sql(expression, "return_type") 3131 return_type = f" RETURNING {return_type}" if return_type else "" 3132 encoding = self.sql(expression, "encoding") 3133 encoding = f" ENCODING {encoding}" if encoding else "" 3134 3135 return self.func( 3136 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3137 *expression.expressions, 3138 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3139 ) 3140 3141 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3142 return self.jsonobject_sql(expression) 3143 3144 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3145 null_handling = expression.args.get("null_handling") 3146 null_handling = f" {null_handling}" if null_handling else "" 3147 return_type = self.sql(expression, "return_type") 3148 return_type = f" RETURNING {return_type}" if return_type else "" 3149 strict = " STRICT" if expression.args.get("strict") else "" 3150 return self.func( 3151 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3152 ) 3153 3154 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3155 this = self.sql(expression, "this") 3156 order = self.sql(expression, "order") 3157 null_handling = expression.args.get("null_handling") 3158 null_handling = f" {null_handling}" if null_handling else "" 3159 return_type = self.sql(expression, "return_type") 3160 return_type = f" RETURNING {return_type}" if return_type else "" 3161 strict = " STRICT" if expression.args.get("strict") else "" 3162 return self.func( 3163 "JSON_ARRAYAGG", 3164 this, 3165 suffix=f"{order}{null_handling}{return_type}{strict})", 3166 ) 3167 3168 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3169 path = self.sql(expression, "path") 3170 path = f" PATH {path}" if path else "" 3171 nested_schema = self.sql(expression, "nested_schema") 3172 3173 if nested_schema: 3174 return f"NESTED{path} {nested_schema}" 3175 3176 this = self.sql(expression, "this") 3177 kind = self.sql(expression, "kind") 3178 kind = f" {kind}" if kind else "" 3179 return f"{this}{kind}{path}" 3180 3181 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3182 return self.func("COLUMNS", *expression.expressions) 3183 3184 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3185 this = self.sql(expression, "this") 3186 path = self.sql(expression, "path") 3187 path = f", {path}" if path else "" 3188 error_handling = expression.args.get("error_handling") 3189 error_handling = f" {error_handling}" if error_handling else "" 3190 empty_handling = expression.args.get("empty_handling") 3191 empty_handling = f" {empty_handling}" if empty_handling else "" 3192 schema = self.sql(expression, "schema") 3193 return self.func( 3194 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3195 ) 3196 3197 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3198 this = self.sql(expression, "this") 3199 kind = self.sql(expression, "kind") 3200 path = self.sql(expression, "path") 3201 path = f" {path}" if path else "" 3202 as_json = " AS JSON" if expression.args.get("as_json") else "" 3203 return f"{this} {kind}{path}{as_json}" 3204 3205 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3206 this = self.sql(expression, "this") 3207 path = self.sql(expression, "path") 3208 path = f", {path}" if path else "" 3209 expressions = self.expressions(expression) 3210 with_ = ( 3211 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3212 if expressions 3213 else "" 3214 ) 3215 return f"OPENJSON({this}{path}){with_}" 3216 3217 def in_sql(self, expression: exp.In) -> str: 3218 query = expression.args.get("query") 3219 unnest = expression.args.get("unnest") 3220 field = expression.args.get("field") 3221 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3222 3223 if query: 3224 in_sql = self.sql(query) 3225 elif unnest: 3226 in_sql = self.in_unnest_op(unnest) 3227 elif field: 3228 in_sql = self.sql(field) 3229 else: 3230 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3231 3232 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3233 3234 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3235 return f"(SELECT {self.sql(unnest)})" 3236 3237 def interval_sql(self, expression: exp.Interval) -> str: 3238 unit = self.sql(expression, "unit") 3239 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3240 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3241 unit = f" {unit}" if unit else "" 3242 3243 if self.SINGLE_STRING_INTERVAL: 3244 this = expression.this.name if expression.this else "" 3245 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3246 3247 this = self.sql(expression, "this") 3248 if this: 3249 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3250 this = f" {this}" if unwrapped else f" ({this})" 3251 3252 return f"INTERVAL{this}{unit}" 3253 3254 def return_sql(self, expression: exp.Return) -> str: 3255 return f"RETURN {self.sql(expression, 'this')}" 3256 3257 def reference_sql(self, expression: exp.Reference) -> str: 3258 this = self.sql(expression, "this") 3259 expressions = self.expressions(expression, flat=True) 3260 expressions = f"({expressions})" if expressions else "" 3261 options = self.expressions(expression, key="options", flat=True, sep=" ") 3262 options = f" {options}" if options else "" 3263 return f"REFERENCES {this}{expressions}{options}" 3264 3265 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3266 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3267 parent = expression.parent 3268 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3269 return self.func( 3270 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3271 ) 3272 3273 def paren_sql(self, expression: exp.Paren) -> str: 3274 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3275 return f"({sql}{self.seg(')', sep='')}" 3276 3277 def neg_sql(self, expression: exp.Neg) -> str: 3278 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3279 this_sql = self.sql(expression, "this") 3280 sep = " " if this_sql[0] == "-" else "" 3281 return f"-{sep}{this_sql}" 3282 3283 def not_sql(self, expression: exp.Not) -> str: 3284 return f"NOT {self.sql(expression, 'this')}" 3285 3286 def alias_sql(self, expression: exp.Alias) -> str: 3287 alias = self.sql(expression, "alias") 3288 alias = f" AS {alias}" if alias else "" 3289 return f"{self.sql(expression, 'this')}{alias}" 3290 3291 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3292 alias = expression.args["alias"] 3293 3294 parent = expression.parent 3295 pivot = parent and parent.parent 3296 3297 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3298 identifier_alias = isinstance(alias, exp.Identifier) 3299 literal_alias = isinstance(alias, exp.Literal) 3300 3301 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3302 alias.replace(exp.Literal.string(alias.output_name)) 3303 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3304 alias.replace(exp.to_identifier(alias.output_name)) 3305 3306 return self.alias_sql(expression) 3307 3308 def aliases_sql(self, expression: exp.Aliases) -> str: 3309 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3310 3311 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3312 this = self.sql(expression, "this") 3313 index = self.sql(expression, "expression") 3314 return f"{this} AT {index}" 3315 3316 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3317 this = self.sql(expression, "this") 3318 zone = self.sql(expression, "zone") 3319 return f"{this} AT TIME ZONE {zone}" 3320 3321 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3322 this = self.sql(expression, "this") 3323 zone = self.sql(expression, "zone") 3324 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3325 3326 def add_sql(self, expression: exp.Add) -> str: 3327 return self.binary(expression, "+") 3328 3329 def and_sql( 3330 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3331 ) -> str: 3332 return self.connector_sql(expression, "AND", stack) 3333 3334 def or_sql( 3335 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3336 ) -> str: 3337 return self.connector_sql(expression, "OR", stack) 3338 3339 def xor_sql( 3340 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3341 ) -> str: 3342 return self.connector_sql(expression, "XOR", stack) 3343 3344 def connector_sql( 3345 self, 3346 expression: exp.Connector, 3347 op: str, 3348 stack: t.Optional[t.List[str | exp.Expression]] = None, 3349 ) -> str: 3350 if stack is not None: 3351 if expression.expressions: 3352 stack.append(self.expressions(expression, sep=f" {op} ")) 3353 else: 3354 stack.append(expression.right) 3355 if expression.comments and self.comments: 3356 for comment in expression.comments: 3357 if comment: 3358 op += f" /*{self.sanitize_comment(comment)}*/" 3359 stack.extend((op, expression.left)) 3360 return op 3361 3362 stack = [expression] 3363 sqls: t.List[str] = [] 3364 ops = set() 3365 3366 while stack: 3367 node = stack.pop() 3368 if isinstance(node, exp.Connector): 3369 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3370 else: 3371 sql = self.sql(node) 3372 if sqls and sqls[-1] in ops: 3373 sqls[-1] += f" {sql}" 3374 else: 3375 sqls.append(sql) 3376 3377 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3378 return sep.join(sqls) 3379 3380 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3381 return self.binary(expression, "&") 3382 3383 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3384 return self.binary(expression, "<<") 3385 3386 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3387 return f"~{self.sql(expression, 'this')}" 3388 3389 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3390 return self.binary(expression, "|") 3391 3392 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3393 return self.binary(expression, ">>") 3394 3395 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3396 return self.binary(expression, "^") 3397 3398 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3399 format_sql = self.sql(expression, "format") 3400 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3401 to_sql = self.sql(expression, "to") 3402 to_sql = f" {to_sql}" if to_sql else "" 3403 action = self.sql(expression, "action") 3404 action = f" {action}" if action else "" 3405 default = self.sql(expression, "default") 3406 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3407 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3408 3409 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3410 zone = self.sql(expression, "this") 3411 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3412 3413 def collate_sql(self, expression: exp.Collate) -> str: 3414 if self.COLLATE_IS_FUNC: 3415 return self.function_fallback_sql(expression) 3416 return self.binary(expression, "COLLATE") 3417 3418 def command_sql(self, expression: exp.Command) -> str: 3419 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3420 3421 def comment_sql(self, expression: exp.Comment) -> str: 3422 this = self.sql(expression, "this") 3423 kind = expression.args["kind"] 3424 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3425 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3426 expression_sql = self.sql(expression, "expression") 3427 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3428 3429 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3430 this = self.sql(expression, "this") 3431 delete = " DELETE" if expression.args.get("delete") else "" 3432 recompress = self.sql(expression, "recompress") 3433 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3434 to_disk = self.sql(expression, "to_disk") 3435 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3436 to_volume = self.sql(expression, "to_volume") 3437 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3438 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3439 3440 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3441 where = self.sql(expression, "where") 3442 group = self.sql(expression, "group") 3443 aggregates = self.expressions(expression, key="aggregates") 3444 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3445 3446 if not (where or group or aggregates) and len(expression.expressions) == 1: 3447 return f"TTL {self.expressions(expression, flat=True)}" 3448 3449 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3450 3451 def transaction_sql(self, expression: exp.Transaction) -> str: 3452 return "BEGIN" 3453 3454 def commit_sql(self, expression: exp.Commit) -> str: 3455 chain = expression.args.get("chain") 3456 if chain is not None: 3457 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3458 3459 return f"COMMIT{chain or ''}" 3460 3461 def rollback_sql(self, expression: exp.Rollback) -> str: 3462 savepoint = expression.args.get("savepoint") 3463 savepoint = f" TO {savepoint}" if savepoint else "" 3464 return f"ROLLBACK{savepoint}" 3465 3466 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3467 this = self.sql(expression, "this") 3468 3469 dtype = self.sql(expression, "dtype") 3470 if dtype: 3471 collate = self.sql(expression, "collate") 3472 collate = f" COLLATE {collate}" if collate else "" 3473 using = self.sql(expression, "using") 3474 using = f" USING {using}" if using else "" 3475 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3476 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3477 3478 default = self.sql(expression, "default") 3479 if default: 3480 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3481 3482 comment = self.sql(expression, "comment") 3483 if comment: 3484 return f"ALTER COLUMN {this} COMMENT {comment}" 3485 3486 visible = expression.args.get("visible") 3487 if visible: 3488 return f"ALTER COLUMN {this} SET {visible}" 3489 3490 allow_null = expression.args.get("allow_null") 3491 drop = expression.args.get("drop") 3492 3493 if not drop and not allow_null: 3494 self.unsupported("Unsupported ALTER COLUMN syntax") 3495 3496 if allow_null is not None: 3497 keyword = "DROP" if drop else "SET" 3498 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3499 3500 return f"ALTER COLUMN {this} DROP DEFAULT" 3501 3502 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3503 this = self.sql(expression, "this") 3504 3505 visible = expression.args.get("visible") 3506 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3507 3508 return f"ALTER INDEX {this} {visible_sql}" 3509 3510 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3511 this = self.sql(expression, "this") 3512 if not isinstance(expression.this, exp.Var): 3513 this = f"KEY DISTKEY {this}" 3514 return f"ALTER DISTSTYLE {this}" 3515 3516 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3517 compound = " COMPOUND" if expression.args.get("compound") else "" 3518 this = self.sql(expression, "this") 3519 expressions = self.expressions(expression, flat=True) 3520 expressions = f"({expressions})" if expressions else "" 3521 return f"ALTER{compound} SORTKEY {this or expressions}" 3522 3523 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3524 if not self.RENAME_TABLE_WITH_DB: 3525 # Remove db from tables 3526 expression = expression.transform( 3527 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3528 ).assert_is(exp.AlterRename) 3529 this = self.sql(expression, "this") 3530 to_kw = " TO" if include_to else "" 3531 return f"RENAME{to_kw} {this}" 3532 3533 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3534 exists = " IF EXISTS" if expression.args.get("exists") else "" 3535 old_column = self.sql(expression, "this") 3536 new_column = self.sql(expression, "to") 3537 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3538 3539 def alterset_sql(self, expression: exp.AlterSet) -> str: 3540 exprs = self.expressions(expression, flat=True) 3541 if self.ALTER_SET_WRAPPED: 3542 exprs = f"({exprs})" 3543 3544 return f"SET {exprs}" 3545 3546 def alter_sql(self, expression: exp.Alter) -> str: 3547 actions = expression.args["actions"] 3548 3549 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3550 actions[0], exp.ColumnDef 3551 ): 3552 actions_sql = self.expressions(expression, key="actions", flat=True) 3553 actions_sql = f"ADD {actions_sql}" 3554 else: 3555 actions_list = [] 3556 for action in actions: 3557 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3558 action_sql = self.add_column_sql(action) 3559 else: 3560 action_sql = self.sql(action) 3561 if isinstance(action, exp.Query): 3562 action_sql = f"AS {action_sql}" 3563 3564 actions_list.append(action_sql) 3565 3566 actions_sql = self.format_args(*actions_list).lstrip("\n") 3567 3568 exists = " IF EXISTS" if expression.args.get("exists") else "" 3569 on_cluster = self.sql(expression, "cluster") 3570 on_cluster = f" {on_cluster}" if on_cluster else "" 3571 only = " ONLY" if expression.args.get("only") else "" 3572 options = self.expressions(expression, key="options") 3573 options = f", {options}" if options else "" 3574 kind = self.sql(expression, "kind") 3575 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3576 check = " WITH CHECK" if expression.args.get("check") else "" 3577 this = self.sql(expression, "this") 3578 this = f" {this}" if this else "" 3579 3580 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3581 3582 def altersession_sql(self, expression: exp.AlterSession) -> str: 3583 items_sql = self.expressions(expression, flat=True) 3584 keyword = "UNSET" if expression.args.get("unset") else "SET" 3585 return f"{keyword} {items_sql}" 3586 3587 def add_column_sql(self, expression: exp.Expression) -> str: 3588 sql = self.sql(expression) 3589 if isinstance(expression, exp.Schema): 3590 column_text = " COLUMNS" 3591 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3592 column_text = " COLUMN" 3593 else: 3594 column_text = "" 3595 3596 return f"ADD{column_text} {sql}" 3597 3598 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3599 expressions = self.expressions(expression) 3600 exists = " IF EXISTS " if expression.args.get("exists") else " " 3601 return f"DROP{exists}{expressions}" 3602 3603 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3604 return f"ADD {self.expressions(expression, indent=False)}" 3605 3606 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3607 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3608 location = self.sql(expression, "location") 3609 location = f" {location}" if location else "" 3610 return f"ADD {exists}{self.sql(expression.this)}{location}" 3611 3612 def distinct_sql(self, expression: exp.Distinct) -> str: 3613 this = self.expressions(expression, flat=True) 3614 3615 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3616 case = exp.case() 3617 for arg in expression.expressions: 3618 case = case.when(arg.is_(exp.null()), exp.null()) 3619 this = self.sql(case.else_(f"({this})")) 3620 3621 this = f" {this}" if this else "" 3622 3623 on = self.sql(expression, "on") 3624 on = f" ON {on}" if on else "" 3625 return f"DISTINCT{this}{on}" 3626 3627 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3628 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3629 3630 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3631 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3632 3633 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3634 this_sql = self.sql(expression, "this") 3635 expression_sql = self.sql(expression, "expression") 3636 kind = "MAX" if expression.args.get("max") else "MIN" 3637 return f"{this_sql} HAVING {kind} {expression_sql}" 3638 3639 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3640 return self.sql( 3641 exp.Cast( 3642 this=exp.Div(this=expression.this, expression=expression.expression), 3643 to=exp.DataType(this=exp.DataType.Type.INT), 3644 ) 3645 ) 3646 3647 def dpipe_sql(self, expression: exp.DPipe) -> str: 3648 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3649 return self.func( 3650 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3651 ) 3652 return self.binary(expression, "||") 3653 3654 def div_sql(self, expression: exp.Div) -> str: 3655 l, r = expression.left, expression.right 3656 3657 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3658 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3659 3660 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3661 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3662 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3663 3664 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3665 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3666 return self.sql( 3667 exp.cast( 3668 l / r, 3669 to=exp.DataType.Type.BIGINT, 3670 ) 3671 ) 3672 3673 return self.binary(expression, "/") 3674 3675 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3676 n = exp._wrap(expression.this, exp.Binary) 3677 d = exp._wrap(expression.expression, exp.Binary) 3678 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3679 3680 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3681 return self.binary(expression, "OVERLAPS") 3682 3683 def distance_sql(self, expression: exp.Distance) -> str: 3684 return self.binary(expression, "<->") 3685 3686 def dot_sql(self, expression: exp.Dot) -> str: 3687 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3688 3689 def eq_sql(self, expression: exp.EQ) -> str: 3690 return self.binary(expression, "=") 3691 3692 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3693 return self.binary(expression, ":=") 3694 3695 def escape_sql(self, expression: exp.Escape) -> str: 3696 return self.binary(expression, "ESCAPE") 3697 3698 def glob_sql(self, expression: exp.Glob) -> str: 3699 return self.binary(expression, "GLOB") 3700 3701 def gt_sql(self, expression: exp.GT) -> str: 3702 return self.binary(expression, ">") 3703 3704 def gte_sql(self, expression: exp.GTE) -> str: 3705 return self.binary(expression, ">=") 3706 3707 def is_sql(self, expression: exp.Is) -> str: 3708 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3709 return self.sql( 3710 expression.this if expression.expression.this else exp.not_(expression.this) 3711 ) 3712 return self.binary(expression, "IS") 3713 3714 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3715 this = expression.this 3716 rhs = expression.expression 3717 3718 if isinstance(expression, exp.Like): 3719 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3720 op = "LIKE" 3721 else: 3722 exp_class = exp.ILike 3723 op = "ILIKE" 3724 3725 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3726 exprs = rhs.this.unnest() 3727 3728 if isinstance(exprs, exp.Tuple): 3729 exprs = exprs.expressions 3730 3731 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3732 3733 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3734 for expr in exprs[1:]: 3735 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3736 3737 return self.sql(like_expr) 3738 3739 return self.binary(expression, op) 3740 3741 def like_sql(self, expression: exp.Like) -> str: 3742 return self._like_sql(expression) 3743 3744 def ilike_sql(self, expression: exp.ILike) -> str: 3745 return self._like_sql(expression) 3746 3747 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3748 return self.binary(expression, "SIMILAR TO") 3749 3750 def lt_sql(self, expression: exp.LT) -> str: 3751 return self.binary(expression, "<") 3752 3753 def lte_sql(self, expression: exp.LTE) -> str: 3754 return self.binary(expression, "<=") 3755 3756 def mod_sql(self, expression: exp.Mod) -> str: 3757 return self.binary(expression, "%") 3758 3759 def mul_sql(self, expression: exp.Mul) -> str: 3760 return self.binary(expression, "*") 3761 3762 def neq_sql(self, expression: exp.NEQ) -> str: 3763 return self.binary(expression, "<>") 3764 3765 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3766 return self.binary(expression, "IS NOT DISTINCT FROM") 3767 3768 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3769 return self.binary(expression, "IS DISTINCT FROM") 3770 3771 def slice_sql(self, expression: exp.Slice) -> str: 3772 return self.binary(expression, ":") 3773 3774 def sub_sql(self, expression: exp.Sub) -> str: 3775 return self.binary(expression, "-") 3776 3777 def trycast_sql(self, expression: exp.TryCast) -> str: 3778 return self.cast_sql(expression, safe_prefix="TRY_") 3779 3780 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3781 return self.cast_sql(expression) 3782 3783 def try_sql(self, expression: exp.Try) -> str: 3784 if not self.TRY_SUPPORTED: 3785 self.unsupported("Unsupported TRY function") 3786 return self.sql(expression, "this") 3787 3788 return self.func("TRY", expression.this) 3789 3790 def log_sql(self, expression: exp.Log) -> str: 3791 this = expression.this 3792 expr = expression.expression 3793 3794 if self.dialect.LOG_BASE_FIRST is False: 3795 this, expr = expr, this 3796 elif self.dialect.LOG_BASE_FIRST is None and expr: 3797 if this.name in ("2", "10"): 3798 return self.func(f"LOG{this.name}", expr) 3799 3800 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3801 3802 return self.func("LOG", this, expr) 3803 3804 def use_sql(self, expression: exp.Use) -> str: 3805 kind = self.sql(expression, "kind") 3806 kind = f" {kind}" if kind else "" 3807 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3808 this = f" {this}" if this else "" 3809 return f"USE{kind}{this}" 3810 3811 def binary(self, expression: exp.Binary, op: str) -> str: 3812 sqls: t.List[str] = [] 3813 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3814 binary_type = type(expression) 3815 3816 while stack: 3817 node = stack.pop() 3818 3819 if type(node) is binary_type: 3820 op_func = node.args.get("operator") 3821 if op_func: 3822 op = f"OPERATOR({self.sql(op_func)})" 3823 3824 stack.append(node.right) 3825 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3826 stack.append(node.left) 3827 else: 3828 sqls.append(self.sql(node)) 3829 3830 return "".join(sqls) 3831 3832 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3833 to_clause = self.sql(expression, "to") 3834 if to_clause: 3835 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3836 3837 return self.function_fallback_sql(expression) 3838 3839 def function_fallback_sql(self, expression: exp.Func) -> str: 3840 args = [] 3841 3842 for key in expression.arg_types: 3843 arg_value = expression.args.get(key) 3844 3845 if isinstance(arg_value, list): 3846 for value in arg_value: 3847 args.append(value) 3848 elif arg_value is not None: 3849 args.append(arg_value) 3850 3851 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3852 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3853 else: 3854 name = expression.sql_name() 3855 3856 return self.func(name, *args) 3857 3858 def func( 3859 self, 3860 name: str, 3861 *args: t.Optional[exp.Expression | str], 3862 prefix: str = "(", 3863 suffix: str = ")", 3864 normalize: bool = True, 3865 ) -> str: 3866 name = self.normalize_func(name) if normalize else name 3867 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3868 3869 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3870 arg_sqls = tuple( 3871 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3872 ) 3873 if self.pretty and self.too_wide(arg_sqls): 3874 return self.indent( 3875 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3876 ) 3877 return sep.join(arg_sqls) 3878 3879 def too_wide(self, args: t.Iterable) -> bool: 3880 return sum(len(arg) for arg in args) > self.max_text_width 3881 3882 def format_time( 3883 self, 3884 expression: exp.Expression, 3885 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3886 inverse_time_trie: t.Optional[t.Dict] = None, 3887 ) -> t.Optional[str]: 3888 return format_time( 3889 self.sql(expression, "format"), 3890 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3891 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3892 ) 3893 3894 def expressions( 3895 self, 3896 expression: t.Optional[exp.Expression] = None, 3897 key: t.Optional[str] = None, 3898 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3899 flat: bool = False, 3900 indent: bool = True, 3901 skip_first: bool = False, 3902 skip_last: bool = False, 3903 sep: str = ", ", 3904 prefix: str = "", 3905 dynamic: bool = False, 3906 new_line: bool = False, 3907 ) -> str: 3908 expressions = expression.args.get(key or "expressions") if expression else sqls 3909 3910 if not expressions: 3911 return "" 3912 3913 if flat: 3914 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3915 3916 num_sqls = len(expressions) 3917 result_sqls = [] 3918 3919 for i, e in enumerate(expressions): 3920 sql = self.sql(e, comment=False) 3921 if not sql: 3922 continue 3923 3924 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3925 3926 if self.pretty: 3927 if self.leading_comma: 3928 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3929 else: 3930 result_sqls.append( 3931 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3932 ) 3933 else: 3934 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3935 3936 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3937 if new_line: 3938 result_sqls.insert(0, "") 3939 result_sqls.append("") 3940 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3941 else: 3942 result_sql = "".join(result_sqls) 3943 3944 return ( 3945 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3946 if indent 3947 else result_sql 3948 ) 3949 3950 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3951 flat = flat or isinstance(expression.parent, exp.Properties) 3952 expressions_sql = self.expressions(expression, flat=flat) 3953 if flat: 3954 return f"{op} {expressions_sql}" 3955 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3956 3957 def naked_property(self, expression: exp.Property) -> str: 3958 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3959 if not property_name: 3960 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3961 return f"{property_name} {self.sql(expression, 'this')}" 3962 3963 def tag_sql(self, expression: exp.Tag) -> str: 3964 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3965 3966 def token_sql(self, token_type: TokenType) -> str: 3967 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3968 3969 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3970 this = self.sql(expression, "this") 3971 expressions = self.no_identify(self.expressions, expression) 3972 expressions = ( 3973 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3974 ) 3975 return f"{this}{expressions}" if expressions.strip() != "" else this 3976 3977 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3978 this = self.sql(expression, "this") 3979 expressions = self.expressions(expression, flat=True) 3980 return f"{this}({expressions})" 3981 3982 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3983 return self.binary(expression, "=>") 3984 3985 def when_sql(self, expression: exp.When) -> str: 3986 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3987 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3988 condition = self.sql(expression, "condition") 3989 condition = f" AND {condition}" if condition else "" 3990 3991 then_expression = expression.args.get("then") 3992 if isinstance(then_expression, exp.Insert): 3993 this = self.sql(then_expression, "this") 3994 this = f"INSERT {this}" if this else "INSERT" 3995 then = self.sql(then_expression, "expression") 3996 then = f"{this} VALUES {then}" if then else this 3997 elif isinstance(then_expression, exp.Update): 3998 if isinstance(then_expression.args.get("expressions"), exp.Star): 3999 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4000 else: 4001 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4002 else: 4003 then = self.sql(then_expression) 4004 return f"WHEN {matched}{source}{condition} THEN {then}" 4005 4006 def whens_sql(self, expression: exp.Whens) -> str: 4007 return self.expressions(expression, sep=" ", indent=False) 4008 4009 def merge_sql(self, expression: exp.Merge) -> str: 4010 table = expression.this 4011 table_alias = "" 4012 4013 hints = table.args.get("hints") 4014 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4015 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4016 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4017 4018 this = self.sql(table) 4019 using = f"USING {self.sql(expression, 'using')}" 4020 on = f"ON {self.sql(expression, 'on')}" 4021 whens = self.sql(expression, "whens") 4022 4023 returning = self.sql(expression, "returning") 4024 if returning: 4025 whens = f"{whens}{returning}" 4026 4027 sep = self.sep() 4028 4029 return self.prepend_ctes( 4030 expression, 4031 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4032 ) 4033 4034 @unsupported_args("format") 4035 def tochar_sql(self, expression: exp.ToChar) -> str: 4036 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4037 4038 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4039 if not self.SUPPORTS_TO_NUMBER: 4040 self.unsupported("Unsupported TO_NUMBER function") 4041 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4042 4043 fmt = expression.args.get("format") 4044 if not fmt: 4045 self.unsupported("Conversion format is required for TO_NUMBER") 4046 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4047 4048 return self.func("TO_NUMBER", expression.this, fmt) 4049 4050 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4051 this = self.sql(expression, "this") 4052 kind = self.sql(expression, "kind") 4053 settings_sql = self.expressions(expression, key="settings", sep=" ") 4054 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4055 return f"{this}({kind}{args})" 4056 4057 def dictrange_sql(self, expression: exp.DictRange) -> str: 4058 this = self.sql(expression, "this") 4059 max = self.sql(expression, "max") 4060 min = self.sql(expression, "min") 4061 return f"{this}(MIN {min} MAX {max})" 4062 4063 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4064 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4065 4066 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4067 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4068 4069 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4070 def uniquekeyproperty_sql( 4071 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4072 ) -> str: 4073 return f"{prefix} ({self.expressions(expression, flat=True)})" 4074 4075 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4076 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4077 expressions = self.expressions(expression, flat=True) 4078 expressions = f" {self.wrap(expressions)}" if expressions else "" 4079 buckets = self.sql(expression, "buckets") 4080 kind = self.sql(expression, "kind") 4081 buckets = f" BUCKETS {buckets}" if buckets else "" 4082 order = self.sql(expression, "order") 4083 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4084 4085 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4086 return "" 4087 4088 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4089 expressions = self.expressions(expression, key="expressions", flat=True) 4090 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4091 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4092 buckets = self.sql(expression, "buckets") 4093 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4094 4095 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4096 this = self.sql(expression, "this") 4097 having = self.sql(expression, "having") 4098 4099 if having: 4100 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4101 4102 return self.func("ANY_VALUE", this) 4103 4104 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4105 transform = self.func("TRANSFORM", *expression.expressions) 4106 row_format_before = self.sql(expression, "row_format_before") 4107 row_format_before = f" {row_format_before}" if row_format_before else "" 4108 record_writer = self.sql(expression, "record_writer") 4109 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4110 using = f" USING {self.sql(expression, 'command_script')}" 4111 schema = self.sql(expression, "schema") 4112 schema = f" AS {schema}" if schema else "" 4113 row_format_after = self.sql(expression, "row_format_after") 4114 row_format_after = f" {row_format_after}" if row_format_after else "" 4115 record_reader = self.sql(expression, "record_reader") 4116 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4117 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4118 4119 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4120 key_block_size = self.sql(expression, "key_block_size") 4121 if key_block_size: 4122 return f"KEY_BLOCK_SIZE = {key_block_size}" 4123 4124 using = self.sql(expression, "using") 4125 if using: 4126 return f"USING {using}" 4127 4128 parser = self.sql(expression, "parser") 4129 if parser: 4130 return f"WITH PARSER {parser}" 4131 4132 comment = self.sql(expression, "comment") 4133 if comment: 4134 return f"COMMENT {comment}" 4135 4136 visible = expression.args.get("visible") 4137 if visible is not None: 4138 return "VISIBLE" if visible else "INVISIBLE" 4139 4140 engine_attr = self.sql(expression, "engine_attr") 4141 if engine_attr: 4142 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4143 4144 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4145 if secondary_engine_attr: 4146 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4147 4148 self.unsupported("Unsupported index constraint option.") 4149 return "" 4150 4151 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4152 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4153 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4154 4155 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4156 kind = self.sql(expression, "kind") 4157 kind = f"{kind} INDEX" if kind else "INDEX" 4158 this = self.sql(expression, "this") 4159 this = f" {this}" if this else "" 4160 index_type = self.sql(expression, "index_type") 4161 index_type = f" USING {index_type}" if index_type else "" 4162 expressions = self.expressions(expression, flat=True) 4163 expressions = f" ({expressions})" if expressions else "" 4164 options = self.expressions(expression, key="options", sep=" ") 4165 options = f" {options}" if options else "" 4166 return f"{kind}{this}{index_type}{expressions}{options}" 4167 4168 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4169 if self.NVL2_SUPPORTED: 4170 return self.function_fallback_sql(expression) 4171 4172 case = exp.Case().when( 4173 expression.this.is_(exp.null()).not_(copy=False), 4174 expression.args["true"], 4175 copy=False, 4176 ) 4177 else_cond = expression.args.get("false") 4178 if else_cond: 4179 case.else_(else_cond, copy=False) 4180 4181 return self.sql(case) 4182 4183 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4184 this = self.sql(expression, "this") 4185 expr = self.sql(expression, "expression") 4186 iterator = self.sql(expression, "iterator") 4187 condition = self.sql(expression, "condition") 4188 condition = f" IF {condition}" if condition else "" 4189 return f"{this} FOR {expr} IN {iterator}{condition}" 4190 4191 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4192 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4193 4194 def opclass_sql(self, expression: exp.Opclass) -> str: 4195 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4196 4197 def predict_sql(self, expression: exp.Predict) -> str: 4198 model = self.sql(expression, "this") 4199 model = f"MODEL {model}" 4200 table = self.sql(expression, "expression") 4201 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4202 parameters = self.sql(expression, "params_struct") 4203 return self.func("PREDICT", model, table, parameters or None) 4204 4205 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4206 model = self.sql(expression, "this") 4207 model = f"MODEL {model}" 4208 table = self.sql(expression, "expression") 4209 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4210 parameters = self.sql(expression, "params_struct") 4211 return self.func("GENERATE_EMBEDDING", model, table, parameters or None) 4212 4213 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4214 this_sql = self.sql(expression, "this") 4215 if isinstance(expression.this, exp.Table): 4216 this_sql = f"TABLE {this_sql}" 4217 4218 return self.func( 4219 "FEATURES_AT_TIME", 4220 this_sql, 4221 expression.args.get("time"), 4222 expression.args.get("num_rows"), 4223 expression.args.get("ignore_feature_nulls"), 4224 ) 4225 4226 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4227 this_sql = self.sql(expression, "this") 4228 if isinstance(expression.this, exp.Table): 4229 this_sql = f"TABLE {this_sql}" 4230 4231 query_table = self.sql(expression, "query_table") 4232 if isinstance(expression.args["query_table"], exp.Table): 4233 query_table = f"TABLE {query_table}" 4234 4235 return self.func( 4236 "VECTOR_SEARCH", 4237 this_sql, 4238 expression.args.get("column_to_search"), 4239 query_table, 4240 expression.args.get("query_column_to_search"), 4241 expression.args.get("top_k"), 4242 expression.args.get("distance_type"), 4243 expression.args.get("options"), 4244 ) 4245 4246 def forin_sql(self, expression: exp.ForIn) -> str: 4247 this = self.sql(expression, "this") 4248 expression_sql = self.sql(expression, "expression") 4249 return f"FOR {this} DO {expression_sql}" 4250 4251 def refresh_sql(self, expression: exp.Refresh) -> str: 4252 this = self.sql(expression, "this") 4253 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4254 return f"REFRESH {table}{this}" 4255 4256 def toarray_sql(self, expression: exp.ToArray) -> str: 4257 arg = expression.this 4258 if not arg.type: 4259 from sqlglot.optimizer.annotate_types import annotate_types 4260 4261 arg = annotate_types(arg, dialect=self.dialect) 4262 4263 if arg.is_type(exp.DataType.Type.ARRAY): 4264 return self.sql(arg) 4265 4266 cond_for_null = arg.is_(exp.null()) 4267 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4268 4269 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4270 this = expression.this 4271 time_format = self.format_time(expression) 4272 4273 if time_format: 4274 return self.sql( 4275 exp.cast( 4276 exp.StrToTime(this=this, format=expression.args["format"]), 4277 exp.DataType.Type.TIME, 4278 ) 4279 ) 4280 4281 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4282 return self.sql(this) 4283 4284 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4285 4286 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4287 this = expression.this 4288 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4289 return self.sql(this) 4290 4291 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4292 4293 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4294 this = expression.this 4295 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4296 return self.sql(this) 4297 4298 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4299 4300 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4301 this = expression.this 4302 time_format = self.format_time(expression) 4303 4304 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4305 return self.sql( 4306 exp.cast( 4307 exp.StrToTime(this=this, format=expression.args["format"]), 4308 exp.DataType.Type.DATE, 4309 ) 4310 ) 4311 4312 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4313 return self.sql(this) 4314 4315 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4316 4317 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4318 return self.sql( 4319 exp.func( 4320 "DATEDIFF", 4321 expression.this, 4322 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4323 "day", 4324 ) 4325 ) 4326 4327 def lastday_sql(self, expression: exp.LastDay) -> str: 4328 if self.LAST_DAY_SUPPORTS_DATE_PART: 4329 return self.function_fallback_sql(expression) 4330 4331 unit = expression.text("unit") 4332 if unit and unit != "MONTH": 4333 self.unsupported("Date parts are not supported in LAST_DAY.") 4334 4335 return self.func("LAST_DAY", expression.this) 4336 4337 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4338 from sqlglot.dialects.dialect import unit_to_str 4339 4340 return self.func( 4341 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4342 ) 4343 4344 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4345 if self.CAN_IMPLEMENT_ARRAY_ANY: 4346 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4347 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4348 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4349 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4350 4351 from sqlglot.dialects import Dialect 4352 4353 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4354 if self.dialect.__class__ != Dialect: 4355 self.unsupported("ARRAY_ANY is unsupported") 4356 4357 return self.function_fallback_sql(expression) 4358 4359 def struct_sql(self, expression: exp.Struct) -> str: 4360 expression.set( 4361 "expressions", 4362 [ 4363 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4364 if isinstance(e, exp.PropertyEQ) 4365 else e 4366 for e in expression.expressions 4367 ], 4368 ) 4369 4370 return self.function_fallback_sql(expression) 4371 4372 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4373 low = self.sql(expression, "this") 4374 high = self.sql(expression, "expression") 4375 4376 return f"{low} TO {high}" 4377 4378 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4379 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4380 tables = f" {self.expressions(expression)}" 4381 4382 exists = " IF EXISTS" if expression.args.get("exists") else "" 4383 4384 on_cluster = self.sql(expression, "cluster") 4385 on_cluster = f" {on_cluster}" if on_cluster else "" 4386 4387 identity = self.sql(expression, "identity") 4388 identity = f" {identity} IDENTITY" if identity else "" 4389 4390 option = self.sql(expression, "option") 4391 option = f" {option}" if option else "" 4392 4393 partition = self.sql(expression, "partition") 4394 partition = f" {partition}" if partition else "" 4395 4396 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4397 4398 # This transpiles T-SQL's CONVERT function 4399 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4400 def convert_sql(self, expression: exp.Convert) -> str: 4401 to = expression.this 4402 value = expression.expression 4403 style = expression.args.get("style") 4404 safe = expression.args.get("safe") 4405 strict = expression.args.get("strict") 4406 4407 if not to or not value: 4408 return "" 4409 4410 # Retrieve length of datatype and override to default if not specified 4411 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4412 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4413 4414 transformed: t.Optional[exp.Expression] = None 4415 cast = exp.Cast if strict else exp.TryCast 4416 4417 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4418 if isinstance(style, exp.Literal) and style.is_int: 4419 from sqlglot.dialects.tsql import TSQL 4420 4421 style_value = style.name 4422 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4423 if not converted_style: 4424 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4425 4426 fmt = exp.Literal.string(converted_style) 4427 4428 if to.this == exp.DataType.Type.DATE: 4429 transformed = exp.StrToDate(this=value, format=fmt) 4430 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4431 transformed = exp.StrToTime(this=value, format=fmt) 4432 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4433 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4434 elif to.this == exp.DataType.Type.TEXT: 4435 transformed = exp.TimeToStr(this=value, format=fmt) 4436 4437 if not transformed: 4438 transformed = cast(this=value, to=to, safe=safe) 4439 4440 return self.sql(transformed) 4441 4442 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4443 this = expression.this 4444 if isinstance(this, exp.JSONPathWildcard): 4445 this = self.json_path_part(this) 4446 return f".{this}" if this else "" 4447 4448 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4449 return f".{this}" 4450 4451 this = self.json_path_part(this) 4452 return ( 4453 f"[{this}]" 4454 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4455 else f".{this}" 4456 ) 4457 4458 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4459 this = self.json_path_part(expression.this) 4460 return f"[{this}]" if this else "" 4461 4462 def _simplify_unless_literal(self, expression: E) -> E: 4463 if not isinstance(expression, exp.Literal): 4464 from sqlglot.optimizer.simplify import simplify 4465 4466 expression = simplify(expression, dialect=self.dialect) 4467 4468 return expression 4469 4470 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4471 this = expression.this 4472 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4473 self.unsupported( 4474 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4475 ) 4476 return self.sql(this) 4477 4478 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4479 # The first modifier here will be the one closest to the AggFunc's arg 4480 mods = sorted( 4481 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4482 key=lambda x: 0 4483 if isinstance(x, exp.HavingMax) 4484 else (1 if isinstance(x, exp.Order) else 2), 4485 ) 4486 4487 if mods: 4488 mod = mods[0] 4489 this = expression.__class__(this=mod.this.copy()) 4490 this.meta["inline"] = True 4491 mod.this.replace(this) 4492 return self.sql(expression.this) 4493 4494 agg_func = expression.find(exp.AggFunc) 4495 4496 if agg_func: 4497 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4498 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4499 4500 return f"{self.sql(expression, 'this')} {text}" 4501 4502 def _replace_line_breaks(self, string: str) -> str: 4503 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4504 if self.pretty: 4505 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4506 return string 4507 4508 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4509 option = self.sql(expression, "this") 4510 4511 if expression.expressions: 4512 upper = option.upper() 4513 4514 # Snowflake FILE_FORMAT options are separated by whitespace 4515 sep = " " if upper == "FILE_FORMAT" else ", " 4516 4517 # Databricks copy/format options do not set their list of values with EQ 4518 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4519 values = self.expressions(expression, flat=True, sep=sep) 4520 return f"{option}{op}({values})" 4521 4522 value = self.sql(expression, "expression") 4523 4524 if not value: 4525 return option 4526 4527 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4528 4529 return f"{option}{op}{value}" 4530 4531 def credentials_sql(self, expression: exp.Credentials) -> str: 4532 cred_expr = expression.args.get("credentials") 4533 if isinstance(cred_expr, exp.Literal): 4534 # Redshift case: CREDENTIALS <string> 4535 credentials = self.sql(expression, "credentials") 4536 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4537 else: 4538 # Snowflake case: CREDENTIALS = (...) 4539 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4540 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4541 4542 storage = self.sql(expression, "storage") 4543 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4544 4545 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4546 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4547 4548 iam_role = self.sql(expression, "iam_role") 4549 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4550 4551 region = self.sql(expression, "region") 4552 region = f" REGION {region}" if region else "" 4553 4554 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4555 4556 def copy_sql(self, expression: exp.Copy) -> str: 4557 this = self.sql(expression, "this") 4558 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4559 4560 credentials = self.sql(expression, "credentials") 4561 credentials = self.seg(credentials) if credentials else "" 4562 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4563 files = self.expressions(expression, key="files", flat=True) 4564 4565 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4566 params = self.expressions( 4567 expression, 4568 key="params", 4569 sep=sep, 4570 new_line=True, 4571 skip_last=True, 4572 skip_first=True, 4573 indent=self.COPY_PARAMS_ARE_WRAPPED, 4574 ) 4575 4576 if params: 4577 if self.COPY_PARAMS_ARE_WRAPPED: 4578 params = f" WITH ({params})" 4579 elif not self.pretty: 4580 params = f" {params}" 4581 4582 return f"COPY{this}{kind} {files}{credentials}{params}" 4583 4584 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4585 return "" 4586 4587 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4588 on_sql = "ON" if expression.args.get("on") else "OFF" 4589 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4590 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4591 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4592 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4593 4594 if filter_col or retention_period: 4595 on_sql = self.func("ON", filter_col, retention_period) 4596 4597 return f"DATA_DELETION={on_sql}" 4598 4599 def maskingpolicycolumnconstraint_sql( 4600 self, expression: exp.MaskingPolicyColumnConstraint 4601 ) -> str: 4602 this = self.sql(expression, "this") 4603 expressions = self.expressions(expression, flat=True) 4604 expressions = f" USING ({expressions})" if expressions else "" 4605 return f"MASKING POLICY {this}{expressions}" 4606 4607 def gapfill_sql(self, expression: exp.GapFill) -> str: 4608 this = self.sql(expression, "this") 4609 this = f"TABLE {this}" 4610 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4611 4612 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4613 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4614 4615 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4616 this = self.sql(expression, "this") 4617 expr = expression.expression 4618 4619 if isinstance(expr, exp.Func): 4620 # T-SQL's CLR functions are case sensitive 4621 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4622 else: 4623 expr = self.sql(expression, "expression") 4624 4625 return self.scope_resolution(expr, this) 4626 4627 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4628 if self.PARSE_JSON_NAME is None: 4629 return self.sql(expression.this) 4630 4631 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4632 4633 def rand_sql(self, expression: exp.Rand) -> str: 4634 lower = self.sql(expression, "lower") 4635 upper = self.sql(expression, "upper") 4636 4637 if lower and upper: 4638 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4639 return self.func("RAND", expression.this) 4640 4641 def changes_sql(self, expression: exp.Changes) -> str: 4642 information = self.sql(expression, "information") 4643 information = f"INFORMATION => {information}" 4644 at_before = self.sql(expression, "at_before") 4645 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4646 end = self.sql(expression, "end") 4647 end = f"{self.seg('')}{end}" if end else "" 4648 4649 return f"CHANGES ({information}){at_before}{end}" 4650 4651 def pad_sql(self, expression: exp.Pad) -> str: 4652 prefix = "L" if expression.args.get("is_left") else "R" 4653 4654 fill_pattern = self.sql(expression, "fill_pattern") or None 4655 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4656 fill_pattern = "' '" 4657 4658 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4659 4660 def summarize_sql(self, expression: exp.Summarize) -> str: 4661 table = " TABLE" if expression.args.get("table") else "" 4662 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4663 4664 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4665 generate_series = exp.GenerateSeries(**expression.args) 4666 4667 parent = expression.parent 4668 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4669 parent = parent.parent 4670 4671 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4672 return self.sql(exp.Unnest(expressions=[generate_series])) 4673 4674 if isinstance(parent, exp.Select): 4675 self.unsupported("GenerateSeries projection unnesting is not supported.") 4676 4677 return self.sql(generate_series) 4678 4679 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4680 exprs = expression.expressions 4681 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4682 if len(exprs) == 0: 4683 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4684 else: 4685 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4686 else: 4687 rhs = self.expressions(expression) # type: ignore 4688 4689 return self.func(name, expression.this, rhs or None) 4690 4691 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4692 if self.SUPPORTS_CONVERT_TIMEZONE: 4693 return self.function_fallback_sql(expression) 4694 4695 source_tz = expression.args.get("source_tz") 4696 target_tz = expression.args.get("target_tz") 4697 timestamp = expression.args.get("timestamp") 4698 4699 if source_tz and timestamp: 4700 timestamp = exp.AtTimeZone( 4701 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4702 ) 4703 4704 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4705 4706 return self.sql(expr) 4707 4708 def json_sql(self, expression: exp.JSON) -> str: 4709 this = self.sql(expression, "this") 4710 this = f" {this}" if this else "" 4711 4712 _with = expression.args.get("with") 4713 4714 if _with is None: 4715 with_sql = "" 4716 elif not _with: 4717 with_sql = " WITHOUT" 4718 else: 4719 with_sql = " WITH" 4720 4721 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4722 4723 return f"JSON{this}{with_sql}{unique_sql}" 4724 4725 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4726 def _generate_on_options(arg: t.Any) -> str: 4727 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4728 4729 path = self.sql(expression, "path") 4730 returning = self.sql(expression, "returning") 4731 returning = f" RETURNING {returning}" if returning else "" 4732 4733 on_condition = self.sql(expression, "on_condition") 4734 on_condition = f" {on_condition}" if on_condition else "" 4735 4736 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4737 4738 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4739 else_ = "ELSE " if expression.args.get("else_") else "" 4740 condition = self.sql(expression, "expression") 4741 condition = f"WHEN {condition} THEN " if condition else else_ 4742 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4743 return f"{condition}{insert}" 4744 4745 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4746 kind = self.sql(expression, "kind") 4747 expressions = self.seg(self.expressions(expression, sep=" ")) 4748 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4749 return res 4750 4751 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4752 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4753 empty = expression.args.get("empty") 4754 empty = ( 4755 f"DEFAULT {empty} ON EMPTY" 4756 if isinstance(empty, exp.Expression) 4757 else self.sql(expression, "empty") 4758 ) 4759 4760 error = expression.args.get("error") 4761 error = ( 4762 f"DEFAULT {error} ON ERROR" 4763 if isinstance(error, exp.Expression) 4764 else self.sql(expression, "error") 4765 ) 4766 4767 if error and empty: 4768 error = ( 4769 f"{empty} {error}" 4770 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4771 else f"{error} {empty}" 4772 ) 4773 empty = "" 4774 4775 null = self.sql(expression, "null") 4776 4777 return f"{empty}{error}{null}" 4778 4779 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4780 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4781 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4782 4783 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4784 this = self.sql(expression, "this") 4785 path = self.sql(expression, "path") 4786 4787 passing = self.expressions(expression, "passing") 4788 passing = f" PASSING {passing}" if passing else "" 4789 4790 on_condition = self.sql(expression, "on_condition") 4791 on_condition = f" {on_condition}" if on_condition else "" 4792 4793 path = f"{path}{passing}{on_condition}" 4794 4795 return self.func("JSON_EXISTS", this, path) 4796 4797 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4798 array_agg = self.function_fallback_sql(expression) 4799 4800 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4801 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4802 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4803 parent = expression.parent 4804 if isinstance(parent, exp.Filter): 4805 parent_cond = parent.expression.this 4806 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4807 else: 4808 this = expression.this 4809 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4810 if this.find(exp.Column): 4811 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4812 this_sql = ( 4813 self.expressions(this) 4814 if isinstance(this, exp.Distinct) 4815 else self.sql(expression, "this") 4816 ) 4817 4818 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4819 4820 return array_agg 4821 4822 def apply_sql(self, expression: exp.Apply) -> str: 4823 this = self.sql(expression, "this") 4824 expr = self.sql(expression, "expression") 4825 4826 return f"{this} APPLY({expr})" 4827 4828 def _grant_or_revoke_sql( 4829 self, 4830 expression: exp.Grant | exp.Revoke, 4831 keyword: str, 4832 preposition: str, 4833 grant_option_prefix: str = "", 4834 grant_option_suffix: str = "", 4835 ) -> str: 4836 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4837 4838 kind = self.sql(expression, "kind") 4839 kind = f" {kind}" if kind else "" 4840 4841 securable = self.sql(expression, "securable") 4842 securable = f" {securable}" if securable else "" 4843 4844 principals = self.expressions(expression, key="principals", flat=True) 4845 4846 if not expression.args.get("grant_option"): 4847 grant_option_prefix = grant_option_suffix = "" 4848 4849 # cascade for revoke only 4850 cascade = self.sql(expression, "cascade") 4851 cascade = f" {cascade}" if cascade else "" 4852 4853 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4854 4855 def grant_sql(self, expression: exp.Grant) -> str: 4856 return self._grant_or_revoke_sql( 4857 expression, 4858 keyword="GRANT", 4859 preposition="TO", 4860 grant_option_suffix=" WITH GRANT OPTION", 4861 ) 4862 4863 def revoke_sql(self, expression: exp.Revoke) -> str: 4864 return self._grant_or_revoke_sql( 4865 expression, 4866 keyword="REVOKE", 4867 preposition="FROM", 4868 grant_option_prefix="GRANT OPTION FOR ", 4869 ) 4870 4871 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4872 this = self.sql(expression, "this") 4873 columns = self.expressions(expression, flat=True) 4874 columns = f"({columns})" if columns else "" 4875 4876 return f"{this}{columns}" 4877 4878 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4879 this = self.sql(expression, "this") 4880 4881 kind = self.sql(expression, "kind") 4882 kind = f"{kind} " if kind else "" 4883 4884 return f"{kind}{this}" 4885 4886 def columns_sql(self, expression: exp.Columns): 4887 func = self.function_fallback_sql(expression) 4888 if expression.args.get("unpack"): 4889 func = f"*{func}" 4890 4891 return func 4892 4893 def overlay_sql(self, expression: exp.Overlay): 4894 this = self.sql(expression, "this") 4895 expr = self.sql(expression, "expression") 4896 from_sql = self.sql(expression, "from") 4897 for_sql = self.sql(expression, "for") 4898 for_sql = f" FOR {for_sql}" if for_sql else "" 4899 4900 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4901 4902 @unsupported_args("format") 4903 def todouble_sql(self, expression: exp.ToDouble) -> str: 4904 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4905 4906 def string_sql(self, expression: exp.String) -> str: 4907 this = expression.this 4908 zone = expression.args.get("zone") 4909 4910 if zone: 4911 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4912 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4913 # set for source_tz to transpile the time conversion before the STRING cast 4914 this = exp.ConvertTimezone( 4915 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4916 ) 4917 4918 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4919 4920 def median_sql(self, expression: exp.Median): 4921 if not self.SUPPORTS_MEDIAN: 4922 return self.sql( 4923 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4924 ) 4925 4926 return self.function_fallback_sql(expression) 4927 4928 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4929 filler = self.sql(expression, "this") 4930 filler = f" {filler}" if filler else "" 4931 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4932 return f"TRUNCATE{filler} {with_count}" 4933 4934 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4935 if self.SUPPORTS_UNIX_SECONDS: 4936 return self.function_fallback_sql(expression) 4937 4938 start_ts = exp.cast( 4939 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4940 ) 4941 4942 return self.sql( 4943 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4944 ) 4945 4946 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4947 dim = expression.expression 4948 4949 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4950 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4951 if not (dim.is_int and dim.name == "1"): 4952 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4953 dim = None 4954 4955 # If dimension is required but not specified, default initialize it 4956 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4957 dim = exp.Literal.number(1) 4958 4959 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4960 4961 def attach_sql(self, expression: exp.Attach) -> str: 4962 this = self.sql(expression, "this") 4963 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4964 expressions = self.expressions(expression) 4965 expressions = f" ({expressions})" if expressions else "" 4966 4967 return f"ATTACH{exists_sql} {this}{expressions}" 4968 4969 def detach_sql(self, expression: exp.Detach) -> str: 4970 this = self.sql(expression, "this") 4971 # the DATABASE keyword is required if IF EXISTS is set 4972 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4973 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4974 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4975 4976 return f"DETACH{exists_sql} {this}" 4977 4978 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4979 this = self.sql(expression, "this") 4980 value = self.sql(expression, "expression") 4981 value = f" {value}" if value else "" 4982 return f"{this}{value}" 4983 4984 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4985 return ( 4986 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4987 ) 4988 4989 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4990 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4991 encode = f"{encode} {self.sql(expression, 'this')}" 4992 4993 properties = expression.args.get("properties") 4994 if properties: 4995 encode = f"{encode} {self.properties(properties)}" 4996 4997 return encode 4998 4999 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5000 this = self.sql(expression, "this") 5001 include = f"INCLUDE {this}" 5002 5003 column_def = self.sql(expression, "column_def") 5004 if column_def: 5005 include = f"{include} {column_def}" 5006 5007 alias = self.sql(expression, "alias") 5008 if alias: 5009 include = f"{include} AS {alias}" 5010 5011 return include 5012 5013 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5014 name = f"NAME {self.sql(expression, 'this')}" 5015 return self.func("XMLELEMENT", name, *expression.expressions) 5016 5017 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5018 this = self.sql(expression, "this") 5019 expr = self.sql(expression, "expression") 5020 expr = f"({expr})" if expr else "" 5021 return f"{this}{expr}" 5022 5023 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5024 partitions = self.expressions(expression, "partition_expressions") 5025 create = self.expressions(expression, "create_expressions") 5026 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5027 5028 def partitionbyrangepropertydynamic_sql( 5029 self, expression: exp.PartitionByRangePropertyDynamic 5030 ) -> str: 5031 start = self.sql(expression, "start") 5032 end = self.sql(expression, "end") 5033 5034 every = expression.args["every"] 5035 if isinstance(every, exp.Interval) and every.this.is_string: 5036 every.this.replace(exp.Literal.number(every.name)) 5037 5038 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5039 5040 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5041 name = self.sql(expression, "this") 5042 values = self.expressions(expression, flat=True) 5043 5044 return f"NAME {name} VALUE {values}" 5045 5046 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5047 kind = self.sql(expression, "kind") 5048 sample = self.sql(expression, "sample") 5049 return f"SAMPLE {sample} {kind}" 5050 5051 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5052 kind = self.sql(expression, "kind") 5053 option = self.sql(expression, "option") 5054 option = f" {option}" if option else "" 5055 this = self.sql(expression, "this") 5056 this = f" {this}" if this else "" 5057 columns = self.expressions(expression) 5058 columns = f" {columns}" if columns else "" 5059 return f"{kind}{option} STATISTICS{this}{columns}" 5060 5061 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5062 this = self.sql(expression, "this") 5063 columns = self.expressions(expression) 5064 inner_expression = self.sql(expression, "expression") 5065 inner_expression = f" {inner_expression}" if inner_expression else "" 5066 update_options = self.sql(expression, "update_options") 5067 update_options = f" {update_options} UPDATE" if update_options else "" 5068 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5069 5070 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5071 kind = self.sql(expression, "kind") 5072 kind = f" {kind}" if kind else "" 5073 return f"DELETE{kind} STATISTICS" 5074 5075 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5076 inner_expression = self.sql(expression, "expression") 5077 return f"LIST CHAINED ROWS{inner_expression}" 5078 5079 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5080 kind = self.sql(expression, "kind") 5081 this = self.sql(expression, "this") 5082 this = f" {this}" if this else "" 5083 inner_expression = self.sql(expression, "expression") 5084 return f"VALIDATE {kind}{this}{inner_expression}" 5085 5086 def analyze_sql(self, expression: exp.Analyze) -> str: 5087 options = self.expressions(expression, key="options", sep=" ") 5088 options = f" {options}" if options else "" 5089 kind = self.sql(expression, "kind") 5090 kind = f" {kind}" if kind else "" 5091 this = self.sql(expression, "this") 5092 this = f" {this}" if this else "" 5093 mode = self.sql(expression, "mode") 5094 mode = f" {mode}" if mode else "" 5095 properties = self.sql(expression, "properties") 5096 properties = f" {properties}" if properties else "" 5097 partition = self.sql(expression, "partition") 5098 partition = f" {partition}" if partition else "" 5099 inner_expression = self.sql(expression, "expression") 5100 inner_expression = f" {inner_expression}" if inner_expression else "" 5101 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5102 5103 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5104 this = self.sql(expression, "this") 5105 namespaces = self.expressions(expression, key="namespaces") 5106 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5107 passing = self.expressions(expression, key="passing") 5108 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5109 columns = self.expressions(expression, key="columns") 5110 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5111 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5112 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5113 5114 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5115 this = self.sql(expression, "this") 5116 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5117 5118 def export_sql(self, expression: exp.Export) -> str: 5119 this = self.sql(expression, "this") 5120 connection = self.sql(expression, "connection") 5121 connection = f"WITH CONNECTION {connection} " if connection else "" 5122 options = self.sql(expression, "options") 5123 return f"EXPORT DATA {connection}{options} AS {this}" 5124 5125 def declare_sql(self, expression: exp.Declare) -> str: 5126 return f"DECLARE {self.expressions(expression, flat=True)}" 5127 5128 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5129 variable = self.sql(expression, "this") 5130 default = self.sql(expression, "default") 5131 default = f" = {default}" if default else "" 5132 5133 kind = self.sql(expression, "kind") 5134 if isinstance(expression.args.get("kind"), exp.Schema): 5135 kind = f"TABLE {kind}" 5136 5137 return f"{variable} AS {kind}{default}" 5138 5139 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5140 kind = self.sql(expression, "kind") 5141 this = self.sql(expression, "this") 5142 set = self.sql(expression, "expression") 5143 using = self.sql(expression, "using") 5144 using = f" USING {using}" if using else "" 5145 5146 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5147 5148 return f"{kind_sql} {this} SET {set}{using}" 5149 5150 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5151 params = self.expressions(expression, key="params", flat=True) 5152 return self.func(expression.name, *expression.expressions) + f"({params})" 5153 5154 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5155 return self.func(expression.name, *expression.expressions) 5156 5157 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5158 return self.anonymousaggfunc_sql(expression) 5159 5160 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5161 return self.parameterizedagg_sql(expression) 5162 5163 def show_sql(self, expression: exp.Show) -> str: 5164 self.unsupported("Unsupported SHOW statement") 5165 return "" 5166 5167 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5168 # Snowflake GET/PUT statements: 5169 # PUT <file> <internalStage> <properties> 5170 # GET <internalStage> <file> <properties> 5171 props = expression.args.get("properties") 5172 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5173 this = self.sql(expression, "this") 5174 target = self.sql(expression, "target") 5175 5176 if isinstance(expression, exp.Put): 5177 return f"PUT {this} {target}{props_sql}" 5178 else: 5179 return f"GET {target} {this}{props_sql}" 5180 5181 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5182 this = self.sql(expression, "this") 5183 expr = self.sql(expression, "expression") 5184 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5185 return f"TRANSLATE({this} USING {expr}{with_error})" 5186 5187 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5188 if self.SUPPORTS_DECODE_CASE: 5189 return self.func("DECODE", *expression.expressions) 5190 5191 expression, *expressions = expression.expressions 5192 5193 ifs = [] 5194 for search, result in zip(expressions[::2], expressions[1::2]): 5195 if isinstance(search, exp.Literal): 5196 ifs.append(exp.If(this=expression.eq(search), true=result)) 5197 elif isinstance(search, exp.Null): 5198 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5199 else: 5200 if isinstance(search, exp.Binary): 5201 search = exp.paren(search) 5202 5203 cond = exp.or_( 5204 expression.eq(search), 5205 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5206 copy=False, 5207 ) 5208 ifs.append(exp.If(this=cond, true=result)) 5209 5210 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5211 return self.sql(case) 5212 5213 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5214 this = self.sql(expression, "this") 5215 this = self.seg(this, sep="") 5216 dimensions = self.expressions( 5217 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5218 ) 5219 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5220 metrics = self.expressions( 5221 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5222 ) 5223 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5224 where = self.sql(expression, "where") 5225 where = self.seg(f"WHERE {where}") if where else "" 5226 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5227 5228 def getextract_sql(self, expression: exp.GetExtract) -> str: 5229 this = expression.this 5230 expr = expression.expression 5231 5232 if not this.type or not expression.type: 5233 from sqlglot.optimizer.annotate_types import annotate_types 5234 5235 this = annotate_types(this, dialect=self.dialect) 5236 5237 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5238 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5239 5240 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5241 5242 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5243 return self.sql( 5244 exp.DateAdd( 5245 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5246 expression=expression.this, 5247 unit=exp.var("DAY"), 5248 ) 5249 ) 5250 5251 def space_sql(self: Generator, expression: exp.Space) -> str: 5252 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5253 5254 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5255 return f"BUILD {self.sql(expression, 'this')}" 5256 5257 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5258 method = self.sql(expression, "method") 5259 kind = expression.args.get("kind") 5260 if not kind: 5261 return f"REFRESH {method}" 5262 5263 every = self.sql(expression, "every") 5264 unit = self.sql(expression, "unit") 5265 every = f" EVERY {every} {unit}" if every else "" 5266 starts = self.sql(expression, "starts") 5267 starts = f" STARTS {starts}" if starts else "" 5268 5269 return f"REFRESH {method} ON {kind}{every}{starts}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.LanguageProperty: lambda self, e: self.naked_property(e), 165 exp.LocationProperty: lambda self, e: self.naked_property(e), 166 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 167 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 168 exp.NonClusteredColumnConstraint: lambda self, 169 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 170 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 171 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 172 exp.OnCommitProperty: lambda _, 173 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 174 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 175 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 176 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 177 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 178 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 179 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 180 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 181 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 182 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 183 exp.ProjectionPolicyColumnConstraint: lambda self, 184 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 185 exp.Put: lambda self, e: self.get_put_sql(e), 186 exp.RemoteWithConnectionModelProperty: lambda self, 187 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 188 exp.ReturnsProperty: lambda self, e: ( 189 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 190 ), 191 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 192 exp.SecureProperty: lambda *_: "SECURE", 193 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 194 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 195 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 196 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 197 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 198 exp.SqlReadWriteProperty: lambda _, e: e.name, 199 exp.SqlSecurityProperty: lambda _, 200 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 201 exp.StabilityProperty: lambda _, e: e.name, 202 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 203 exp.StreamingTableProperty: lambda *_: "STREAMING", 204 exp.StrictProperty: lambda *_: "STRICT", 205 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 206 exp.TableColumn: lambda self, e: self.sql(e.this), 207 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 208 exp.TemporaryProperty: lambda *_: "TEMPORARY", 209 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 210 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 211 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 212 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 213 exp.TransientProperty: lambda *_: "TRANSIENT", 214 exp.Union: lambda self, e: self.set_operations(e), 215 exp.UnloggedProperty: lambda *_: "UNLOGGED", 216 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 217 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 218 exp.Uuid: lambda *_: "UUID()", 219 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 220 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 221 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 222 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 223 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 224 exp.VolatileProperty: lambda *_: "VOLATILE", 225 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 226 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 227 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 228 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 229 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 230 exp.ForceProperty: lambda *_: "FORCE", 231 } 232 233 # Whether null ordering is supported in order by 234 # True: Full Support, None: No support, False: No support for certain cases 235 # such as window specifications, aggregate functions etc 236 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 237 238 # Whether ignore nulls is inside the agg or outside. 239 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 240 IGNORE_NULLS_IN_FUNC = False 241 242 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 243 LOCKING_READS_SUPPORTED = False 244 245 # Whether the EXCEPT and INTERSECT operations can return duplicates 246 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 247 248 # Wrap derived values in parens, usually standard but spark doesn't support it 249 WRAP_DERIVED_VALUES = True 250 251 # Whether create function uses an AS before the RETURN 252 CREATE_FUNCTION_RETURN_AS = True 253 254 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 255 MATCHED_BY_SOURCE = True 256 257 # Whether the INTERVAL expression works only with values like '1 day' 258 SINGLE_STRING_INTERVAL = False 259 260 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 261 INTERVAL_ALLOWS_PLURAL_FORM = True 262 263 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 264 LIMIT_FETCH = "ALL" 265 266 # Whether limit and fetch allows expresions or just limits 267 LIMIT_ONLY_LITERALS = False 268 269 # Whether a table is allowed to be renamed with a db 270 RENAME_TABLE_WITH_DB = True 271 272 # The separator for grouping sets and rollups 273 GROUPINGS_SEP = "," 274 275 # The string used for creating an index on a table 276 INDEX_ON = "ON" 277 278 # Whether join hints should be generated 279 JOIN_HINTS = True 280 281 # Whether table hints should be generated 282 TABLE_HINTS = True 283 284 # Whether query hints should be generated 285 QUERY_HINTS = True 286 287 # What kind of separator to use for query hints 288 QUERY_HINT_SEP = ", " 289 290 # Whether comparing against booleans (e.g. x IS TRUE) is supported 291 IS_BOOL_ALLOWED = True 292 293 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 294 DUPLICATE_KEY_UPDATE_WITH_SET = True 295 296 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 297 LIMIT_IS_TOP = False 298 299 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 300 RETURNING_END = True 301 302 # Whether to generate an unquoted value for EXTRACT's date part argument 303 EXTRACT_ALLOWS_QUOTES = True 304 305 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 306 TZ_TO_WITH_TIME_ZONE = False 307 308 # Whether the NVL2 function is supported 309 NVL2_SUPPORTED = True 310 311 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 312 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 313 314 # Whether VALUES statements can be used as derived tables. 315 # MySQL 5 and Redshift do not allow this, so when False, it will convert 316 # SELECT * VALUES into SELECT UNION 317 VALUES_AS_TABLE = True 318 319 # Whether the word COLUMN is included when adding a column with ALTER TABLE 320 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 321 322 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 323 UNNEST_WITH_ORDINALITY = True 324 325 # Whether FILTER (WHERE cond) can be used for conditional aggregation 326 AGGREGATE_FILTER_SUPPORTED = True 327 328 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 329 SEMI_ANTI_JOIN_WITH_SIDE = True 330 331 # Whether to include the type of a computed column in the CREATE DDL 332 COMPUTED_COLUMN_WITH_TYPE = True 333 334 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 335 SUPPORTS_TABLE_COPY = True 336 337 # Whether parentheses are required around the table sample's expression 338 TABLESAMPLE_REQUIRES_PARENS = True 339 340 # Whether a table sample clause's size needs to be followed by the ROWS keyword 341 TABLESAMPLE_SIZE_IS_ROWS = True 342 343 # The keyword(s) to use when generating a sample clause 344 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 345 346 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 347 TABLESAMPLE_WITH_METHOD = True 348 349 # The keyword to use when specifying the seed of a sample clause 350 TABLESAMPLE_SEED_KEYWORD = "SEED" 351 352 # Whether COLLATE is a function instead of a binary operator 353 COLLATE_IS_FUNC = False 354 355 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 356 DATA_TYPE_SPECIFIERS_ALLOWED = False 357 358 # Whether conditions require booleans WHERE x = 0 vs WHERE x 359 ENSURE_BOOLS = False 360 361 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 362 CTE_RECURSIVE_KEYWORD_REQUIRED = True 363 364 # Whether CONCAT requires >1 arguments 365 SUPPORTS_SINGLE_ARG_CONCAT = True 366 367 # Whether LAST_DAY function supports a date part argument 368 LAST_DAY_SUPPORTS_DATE_PART = True 369 370 # Whether named columns are allowed in table aliases 371 SUPPORTS_TABLE_ALIAS_COLUMNS = True 372 373 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 374 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 375 376 # What delimiter to use for separating JSON key/value pairs 377 JSON_KEY_VALUE_PAIR_SEP = ":" 378 379 # INSERT OVERWRITE TABLE x override 380 INSERT_OVERWRITE = " OVERWRITE TABLE" 381 382 # Whether the SELECT .. INTO syntax is used instead of CTAS 383 SUPPORTS_SELECT_INTO = False 384 385 # Whether UNLOGGED tables can be created 386 SUPPORTS_UNLOGGED_TABLES = False 387 388 # Whether the CREATE TABLE LIKE statement is supported 389 SUPPORTS_CREATE_TABLE_LIKE = True 390 391 # Whether the LikeProperty needs to be specified inside of the schema clause 392 LIKE_PROPERTY_INSIDE_SCHEMA = False 393 394 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 395 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 396 MULTI_ARG_DISTINCT = True 397 398 # Whether the JSON extraction operators expect a value of type JSON 399 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 400 401 # Whether bracketed keys like ["foo"] are supported in JSON paths 402 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 403 404 # Whether to escape keys using single quotes in JSON paths 405 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 406 407 # The JSONPathPart expressions supported by this dialect 408 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 409 410 # Whether any(f(x) for x in array) can be implemented by this dialect 411 CAN_IMPLEMENT_ARRAY_ANY = False 412 413 # Whether the function TO_NUMBER is supported 414 SUPPORTS_TO_NUMBER = True 415 416 # Whether EXCLUDE in window specification is supported 417 SUPPORTS_WINDOW_EXCLUDE = False 418 419 # Whether or not set op modifiers apply to the outer set op or select. 420 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 421 # True means limit 1 happens after the set op, False means it it happens on y. 422 SET_OP_MODIFIERS = True 423 424 # Whether parameters from COPY statement are wrapped in parentheses 425 COPY_PARAMS_ARE_WRAPPED = True 426 427 # Whether values of params are set with "=" token or empty space 428 COPY_PARAMS_EQ_REQUIRED = False 429 430 # Whether COPY statement has INTO keyword 431 COPY_HAS_INTO_KEYWORD = True 432 433 # Whether the conditional TRY(expression) function is supported 434 TRY_SUPPORTED = True 435 436 # Whether the UESCAPE syntax in unicode strings is supported 437 SUPPORTS_UESCAPE = True 438 439 # Function used to replace escaped unicode codes in unicode strings 440 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 441 442 # The keyword to use when generating a star projection with excluded columns 443 STAR_EXCEPT = "EXCEPT" 444 445 # The HEX function name 446 HEX_FUNC = "HEX" 447 448 # The keywords to use when prefixing & separating WITH based properties 449 WITH_PROPERTIES_PREFIX = "WITH" 450 451 # Whether to quote the generated expression of exp.JsonPath 452 QUOTE_JSON_PATH = True 453 454 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 455 PAD_FILL_PATTERN_IS_REQUIRED = False 456 457 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 458 SUPPORTS_EXPLODING_PROJECTIONS = True 459 460 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 461 ARRAY_CONCAT_IS_VAR_LEN = True 462 463 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 464 SUPPORTS_CONVERT_TIMEZONE = False 465 466 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 467 SUPPORTS_MEDIAN = True 468 469 # Whether UNIX_SECONDS(timestamp) is supported 470 SUPPORTS_UNIX_SECONDS = False 471 472 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 473 ALTER_SET_WRAPPED = False 474 475 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 476 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 477 # TODO: The normalization should be done by default once we've tested it across all dialects. 478 NORMALIZE_EXTRACT_DATE_PARTS = False 479 480 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 481 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 482 483 # The function name of the exp.ArraySize expression 484 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 485 486 # The syntax to use when altering the type of a column 487 ALTER_SET_TYPE = "SET DATA TYPE" 488 489 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 490 # None -> Doesn't support it at all 491 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 492 # True (Postgres) -> Explicitly requires it 493 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 494 495 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 496 SUPPORTS_DECODE_CASE = True 497 498 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 499 SUPPORTS_BETWEEN_FLAGS = False 500 501 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 502 SUPPORTS_LIKE_QUANTIFIERS = True 503 504 TYPE_MAPPING = { 505 exp.DataType.Type.DATETIME2: "TIMESTAMP", 506 exp.DataType.Type.NCHAR: "CHAR", 507 exp.DataType.Type.NVARCHAR: "VARCHAR", 508 exp.DataType.Type.MEDIUMTEXT: "TEXT", 509 exp.DataType.Type.LONGTEXT: "TEXT", 510 exp.DataType.Type.TINYTEXT: "TEXT", 511 exp.DataType.Type.BLOB: "VARBINARY", 512 exp.DataType.Type.MEDIUMBLOB: "BLOB", 513 exp.DataType.Type.LONGBLOB: "BLOB", 514 exp.DataType.Type.TINYBLOB: "BLOB", 515 exp.DataType.Type.INET: "INET", 516 exp.DataType.Type.ROWVERSION: "VARBINARY", 517 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 518 } 519 520 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 521 522 TIME_PART_SINGULARS = { 523 "MICROSECONDS": "MICROSECOND", 524 "SECONDS": "SECOND", 525 "MINUTES": "MINUTE", 526 "HOURS": "HOUR", 527 "DAYS": "DAY", 528 "WEEKS": "WEEK", 529 "MONTHS": "MONTH", 530 "QUARTERS": "QUARTER", 531 "YEARS": "YEAR", 532 } 533 534 AFTER_HAVING_MODIFIER_TRANSFORMS = { 535 "cluster": lambda self, e: self.sql(e, "cluster"), 536 "distribute": lambda self, e: self.sql(e, "distribute"), 537 "sort": lambda self, e: self.sql(e, "sort"), 538 "windows": lambda self, e: ( 539 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 540 if e.args.get("windows") 541 else "" 542 ), 543 "qualify": lambda self, e: self.sql(e, "qualify"), 544 } 545 546 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 547 548 STRUCT_DELIMITER = ("<", ">") 549 550 PARAMETER_TOKEN = "@" 551 NAMED_PLACEHOLDER_TOKEN = ":" 552 553 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 554 555 PROPERTIES_LOCATION = { 556 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 558 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 562 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 564 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 567 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 571 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 573 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 574 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 576 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 580 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 584 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 585 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 586 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 587 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 588 exp.HeapProperty: exp.Properties.Location.POST_WITH, 589 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 591 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 594 exp.JournalProperty: exp.Properties.Location.POST_NAME, 595 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 600 exp.LogProperty: exp.Properties.Location.POST_NAME, 601 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 602 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 603 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 604 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 606 exp.Order: exp.Properties.Location.POST_SCHEMA, 607 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 609 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 611 exp.Property: exp.Properties.Location.POST_WITH, 612 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 620 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 622 exp.Set: exp.Properties.Location.POST_SCHEMA, 623 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.SetProperty: exp.Properties.Location.POST_CREATE, 625 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 627 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 628 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 631 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 634 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.Tags: exp.Properties.Location.POST_WITH, 636 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 637 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 639 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 640 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 641 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 642 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 645 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 646 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 647 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 648 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 650 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 651 } 652 653 # Keywords that can't be used as unquoted identifier names 654 RESERVED_KEYWORDS: t.Set[str] = set() 655 656 # Expressions whose comments are separated from them for better formatting 657 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 658 exp.Command, 659 exp.Create, 660 exp.Describe, 661 exp.Delete, 662 exp.Drop, 663 exp.From, 664 exp.Insert, 665 exp.Join, 666 exp.MultitableInserts, 667 exp.Order, 668 exp.Group, 669 exp.Having, 670 exp.Select, 671 exp.SetOperation, 672 exp.Update, 673 exp.Where, 674 exp.With, 675 ) 676 677 # Expressions that should not have their comments generated in maybe_comment 678 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 679 exp.Binary, 680 exp.SetOperation, 681 ) 682 683 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 684 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 685 exp.Column, 686 exp.Literal, 687 exp.Neg, 688 exp.Paren, 689 ) 690 691 PARAMETERIZABLE_TEXT_TYPES = { 692 exp.DataType.Type.NVARCHAR, 693 exp.DataType.Type.VARCHAR, 694 exp.DataType.Type.CHAR, 695 exp.DataType.Type.NCHAR, 696 } 697 698 # Expressions that need to have all CTEs under them bubbled up to them 699 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 700 701 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 702 703 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 704 705 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 706 707 __slots__ = ( 708 "pretty", 709 "identify", 710 "normalize", 711 "pad", 712 "_indent", 713 "normalize_functions", 714 "unsupported_level", 715 "max_unsupported", 716 "leading_comma", 717 "max_text_width", 718 "comments", 719 "dialect", 720 "unsupported_messages", 721 "_escaped_quote_end", 722 "_escaped_identifier_end", 723 "_next_name", 724 "_identifier_start", 725 "_identifier_end", 726 "_quote_json_path_key_using_brackets", 727 ) 728 729 def __init__( 730 self, 731 pretty: t.Optional[bool] = None, 732 identify: str | bool = False, 733 normalize: bool = False, 734 pad: int = 2, 735 indent: int = 2, 736 normalize_functions: t.Optional[str | bool] = None, 737 unsupported_level: ErrorLevel = ErrorLevel.WARN, 738 max_unsupported: int = 3, 739 leading_comma: bool = False, 740 max_text_width: int = 80, 741 comments: bool = True, 742 dialect: DialectType = None, 743 ): 744 import sqlglot 745 from sqlglot.dialects import Dialect 746 747 self.pretty = pretty if pretty is not None else sqlglot.pretty 748 self.identify = identify 749 self.normalize = normalize 750 self.pad = pad 751 self._indent = indent 752 self.unsupported_level = unsupported_level 753 self.max_unsupported = max_unsupported 754 self.leading_comma = leading_comma 755 self.max_text_width = max_text_width 756 self.comments = comments 757 self.dialect = Dialect.get_or_raise(dialect) 758 759 # This is both a Dialect property and a Generator argument, so we prioritize the latter 760 self.normalize_functions = ( 761 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 762 ) 763 764 self.unsupported_messages: t.List[str] = [] 765 self._escaped_quote_end: str = ( 766 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 767 ) 768 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 769 770 self._next_name = name_sequence("_t") 771 772 self._identifier_start = self.dialect.IDENTIFIER_START 773 self._identifier_end = self.dialect.IDENTIFIER_END 774 775 self._quote_json_path_key_using_brackets = True 776 777 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 778 """ 779 Generates the SQL string corresponding to the given syntax tree. 780 781 Args: 782 expression: The syntax tree. 783 copy: Whether to copy the expression. The generator performs mutations so 784 it is safer to copy. 785 786 Returns: 787 The SQL string corresponding to `expression`. 788 """ 789 if copy: 790 expression = expression.copy() 791 792 expression = self.preprocess(expression) 793 794 self.unsupported_messages = [] 795 sql = self.sql(expression).strip() 796 797 if self.pretty: 798 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 799 800 if self.unsupported_level == ErrorLevel.IGNORE: 801 return sql 802 803 if self.unsupported_level == ErrorLevel.WARN: 804 for msg in self.unsupported_messages: 805 logger.warning(msg) 806 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 807 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 808 809 return sql 810 811 def preprocess(self, expression: exp.Expression) -> exp.Expression: 812 """Apply generic preprocessing transformations to a given expression.""" 813 expression = self._move_ctes_to_top_level(expression) 814 815 if self.ENSURE_BOOLS: 816 from sqlglot.transforms import ensure_bools 817 818 expression = ensure_bools(expression) 819 820 return expression 821 822 def _move_ctes_to_top_level(self, expression: E) -> E: 823 if ( 824 not expression.parent 825 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 826 and any(node.parent is not expression for node in expression.find_all(exp.With)) 827 ): 828 from sqlglot.transforms import move_ctes_to_top_level 829 830 expression = move_ctes_to_top_level(expression) 831 return expression 832 833 def unsupported(self, message: str) -> None: 834 if self.unsupported_level == ErrorLevel.IMMEDIATE: 835 raise UnsupportedError(message) 836 self.unsupported_messages.append(message) 837 838 def sep(self, sep: str = " ") -> str: 839 return f"{sep.strip()}\n" if self.pretty else sep 840 841 def seg(self, sql: str, sep: str = " ") -> str: 842 return f"{self.sep(sep)}{sql}" 843 844 def sanitize_comment(self, comment: str) -> str: 845 comment = " " + comment if comment[0].strip() else comment 846 comment = comment + " " if comment[-1].strip() else comment 847 848 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 849 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 850 comment = comment.replace("*/", "* /") 851 852 return comment 853 854 def maybe_comment( 855 self, 856 sql: str, 857 expression: t.Optional[exp.Expression] = None, 858 comments: t.Optional[t.List[str]] = None, 859 separated: bool = False, 860 ) -> str: 861 comments = ( 862 ((expression and expression.comments) if comments is None else comments) # type: ignore 863 if self.comments 864 else None 865 ) 866 867 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 868 return sql 869 870 comments_sql = " ".join( 871 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 872 ) 873 874 if not comments_sql: 875 return sql 876 877 comments_sql = self._replace_line_breaks(comments_sql) 878 879 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 880 return ( 881 f"{self.sep()}{comments_sql}{sql}" 882 if not sql or sql[0].isspace() 883 else f"{comments_sql}{self.sep()}{sql}" 884 ) 885 886 return f"{sql} {comments_sql}" 887 888 def wrap(self, expression: exp.Expression | str) -> str: 889 this_sql = ( 890 self.sql(expression) 891 if isinstance(expression, exp.UNWRAPPED_QUERIES) 892 else self.sql(expression, "this") 893 ) 894 if not this_sql: 895 return "()" 896 897 this_sql = self.indent(this_sql, level=1, pad=0) 898 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 899 900 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 901 original = self.identify 902 self.identify = False 903 result = func(*args, **kwargs) 904 self.identify = original 905 return result 906 907 def normalize_func(self, name: str) -> str: 908 if self.normalize_functions == "upper" or self.normalize_functions is True: 909 return name.upper() 910 if self.normalize_functions == "lower": 911 return name.lower() 912 return name 913 914 def indent( 915 self, 916 sql: str, 917 level: int = 0, 918 pad: t.Optional[int] = None, 919 skip_first: bool = False, 920 skip_last: bool = False, 921 ) -> str: 922 if not self.pretty or not sql: 923 return sql 924 925 pad = self.pad if pad is None else pad 926 lines = sql.split("\n") 927 928 return "\n".join( 929 ( 930 line 931 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 932 else f"{' ' * (level * self._indent + pad)}{line}" 933 ) 934 for i, line in enumerate(lines) 935 ) 936 937 def sql( 938 self, 939 expression: t.Optional[str | exp.Expression], 940 key: t.Optional[str] = None, 941 comment: bool = True, 942 ) -> str: 943 if not expression: 944 return "" 945 946 if isinstance(expression, str): 947 return expression 948 949 if key: 950 value = expression.args.get(key) 951 if value: 952 return self.sql(value) 953 return "" 954 955 transform = self.TRANSFORMS.get(expression.__class__) 956 957 if callable(transform): 958 sql = transform(self, expression) 959 elif isinstance(expression, exp.Expression): 960 exp_handler_name = f"{expression.key}_sql" 961 962 if hasattr(self, exp_handler_name): 963 sql = getattr(self, exp_handler_name)(expression) 964 elif isinstance(expression, exp.Func): 965 sql = self.function_fallback_sql(expression) 966 elif isinstance(expression, exp.Property): 967 sql = self.property_sql(expression) 968 else: 969 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 970 else: 971 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 972 973 return self.maybe_comment(sql, expression) if self.comments and comment else sql 974 975 def uncache_sql(self, expression: exp.Uncache) -> str: 976 table = self.sql(expression, "this") 977 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 978 return f"UNCACHE TABLE{exists_sql} {table}" 979 980 def cache_sql(self, expression: exp.Cache) -> str: 981 lazy = " LAZY" if expression.args.get("lazy") else "" 982 table = self.sql(expression, "this") 983 options = expression.args.get("options") 984 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 985 sql = self.sql(expression, "expression") 986 sql = f" AS{self.sep()}{sql}" if sql else "" 987 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 988 return self.prepend_ctes(expression, sql) 989 990 def characterset_sql(self, expression: exp.CharacterSet) -> str: 991 if isinstance(expression.parent, exp.Cast): 992 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 993 default = "DEFAULT " if expression.args.get("default") else "" 994 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 995 996 def column_parts(self, expression: exp.Column) -> str: 997 return ".".join( 998 self.sql(part) 999 for part in ( 1000 expression.args.get("catalog"), 1001 expression.args.get("db"), 1002 expression.args.get("table"), 1003 expression.args.get("this"), 1004 ) 1005 if part 1006 ) 1007 1008 def column_sql(self, expression: exp.Column) -> str: 1009 join_mark = " (+)" if expression.args.get("join_mark") else "" 1010 1011 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1012 join_mark = "" 1013 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1014 1015 return f"{self.column_parts(expression)}{join_mark}" 1016 1017 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1018 this = self.sql(expression, "this") 1019 this = f" {this}" if this else "" 1020 position = self.sql(expression, "position") 1021 return f"{position}{this}" 1022 1023 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1024 column = self.sql(expression, "this") 1025 kind = self.sql(expression, "kind") 1026 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1027 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1028 kind = f"{sep}{kind}" if kind else "" 1029 constraints = f" {constraints}" if constraints else "" 1030 position = self.sql(expression, "position") 1031 position = f" {position}" if position else "" 1032 1033 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1034 kind = "" 1035 1036 return f"{exists}{column}{kind}{constraints}{position}" 1037 1038 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1039 this = self.sql(expression, "this") 1040 kind_sql = self.sql(expression, "kind").strip() 1041 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1042 1043 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1044 this = self.sql(expression, "this") 1045 if expression.args.get("not_null"): 1046 persisted = " PERSISTED NOT NULL" 1047 elif expression.args.get("persisted"): 1048 persisted = " PERSISTED" 1049 else: 1050 persisted = "" 1051 1052 return f"AS {this}{persisted}" 1053 1054 def autoincrementcolumnconstraint_sql(self, _) -> str: 1055 return self.token_sql(TokenType.AUTO_INCREMENT) 1056 1057 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1058 if isinstance(expression.this, list): 1059 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1060 else: 1061 this = self.sql(expression, "this") 1062 1063 return f"COMPRESS {this}" 1064 1065 def generatedasidentitycolumnconstraint_sql( 1066 self, expression: exp.GeneratedAsIdentityColumnConstraint 1067 ) -> str: 1068 this = "" 1069 if expression.this is not None: 1070 on_null = " ON NULL" if expression.args.get("on_null") else "" 1071 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1072 1073 start = expression.args.get("start") 1074 start = f"START WITH {start}" if start else "" 1075 increment = expression.args.get("increment") 1076 increment = f" INCREMENT BY {increment}" if increment else "" 1077 minvalue = expression.args.get("minvalue") 1078 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1079 maxvalue = expression.args.get("maxvalue") 1080 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1081 cycle = expression.args.get("cycle") 1082 cycle_sql = "" 1083 1084 if cycle is not None: 1085 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1086 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1087 1088 sequence_opts = "" 1089 if start or increment or cycle_sql: 1090 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1091 sequence_opts = f" ({sequence_opts.strip()})" 1092 1093 expr = self.sql(expression, "expression") 1094 expr = f"({expr})" if expr else "IDENTITY" 1095 1096 return f"GENERATED{this} AS {expr}{sequence_opts}" 1097 1098 def generatedasrowcolumnconstraint_sql( 1099 self, expression: exp.GeneratedAsRowColumnConstraint 1100 ) -> str: 1101 start = "START" if expression.args.get("start") else "END" 1102 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1103 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1104 1105 def periodforsystemtimeconstraint_sql( 1106 self, expression: exp.PeriodForSystemTimeConstraint 1107 ) -> str: 1108 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1109 1110 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1111 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1112 1113 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1114 desc = expression.args.get("desc") 1115 if desc is not None: 1116 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1117 options = self.expressions(expression, key="options", flat=True, sep=" ") 1118 options = f" {options}" if options else "" 1119 return f"PRIMARY KEY{options}" 1120 1121 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1122 this = self.sql(expression, "this") 1123 this = f" {this}" if this else "" 1124 index_type = expression.args.get("index_type") 1125 index_type = f" USING {index_type}" if index_type else "" 1126 on_conflict = self.sql(expression, "on_conflict") 1127 on_conflict = f" {on_conflict}" if on_conflict else "" 1128 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1129 options = self.expressions(expression, key="options", flat=True, sep=" ") 1130 options = f" {options}" if options else "" 1131 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1132 1133 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1134 return self.sql(expression, "this") 1135 1136 def create_sql(self, expression: exp.Create) -> str: 1137 kind = self.sql(expression, "kind") 1138 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1139 properties = expression.args.get("properties") 1140 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1141 1142 this = self.createable_sql(expression, properties_locs) 1143 1144 properties_sql = "" 1145 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1146 exp.Properties.Location.POST_WITH 1147 ): 1148 props_ast = exp.Properties( 1149 expressions=[ 1150 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1151 *properties_locs[exp.Properties.Location.POST_WITH], 1152 ] 1153 ) 1154 props_ast.parent = expression 1155 properties_sql = self.sql(props_ast) 1156 1157 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1158 properties_sql = self.sep() + properties_sql 1159 elif not self.pretty: 1160 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1161 properties_sql = f" {properties_sql}" 1162 1163 begin = " BEGIN" if expression.args.get("begin") else "" 1164 end = " END" if expression.args.get("end") else "" 1165 1166 expression_sql = self.sql(expression, "expression") 1167 if expression_sql: 1168 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1169 1170 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1171 postalias_props_sql = "" 1172 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1173 postalias_props_sql = self.properties( 1174 exp.Properties( 1175 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1176 ), 1177 wrapped=False, 1178 ) 1179 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1180 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1181 1182 postindex_props_sql = "" 1183 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1184 postindex_props_sql = self.properties( 1185 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1186 wrapped=False, 1187 prefix=" ", 1188 ) 1189 1190 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1191 indexes = f" {indexes}" if indexes else "" 1192 index_sql = indexes + postindex_props_sql 1193 1194 replace = " OR REPLACE" if expression.args.get("replace") else "" 1195 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1196 unique = " UNIQUE" if expression.args.get("unique") else "" 1197 1198 clustered = expression.args.get("clustered") 1199 if clustered is None: 1200 clustered_sql = "" 1201 elif clustered: 1202 clustered_sql = " CLUSTERED COLUMNSTORE" 1203 else: 1204 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1205 1206 postcreate_props_sql = "" 1207 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1208 postcreate_props_sql = self.properties( 1209 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1210 sep=" ", 1211 prefix=" ", 1212 wrapped=False, 1213 ) 1214 1215 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1216 1217 postexpression_props_sql = "" 1218 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1219 postexpression_props_sql = self.properties( 1220 exp.Properties( 1221 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1222 ), 1223 sep=" ", 1224 prefix=" ", 1225 wrapped=False, 1226 ) 1227 1228 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1229 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1230 no_schema_binding = ( 1231 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1232 ) 1233 1234 clone = self.sql(expression, "clone") 1235 clone = f" {clone}" if clone else "" 1236 1237 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1238 properties_expression = f"{expression_sql}{properties_sql}" 1239 else: 1240 properties_expression = f"{properties_sql}{expression_sql}" 1241 1242 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1243 return self.prepend_ctes(expression, expression_sql) 1244 1245 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1246 start = self.sql(expression, "start") 1247 start = f"START WITH {start}" if start else "" 1248 increment = self.sql(expression, "increment") 1249 increment = f" INCREMENT BY {increment}" if increment else "" 1250 minvalue = self.sql(expression, "minvalue") 1251 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1252 maxvalue = self.sql(expression, "maxvalue") 1253 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1254 owned = self.sql(expression, "owned") 1255 owned = f" OWNED BY {owned}" if owned else "" 1256 1257 cache = expression.args.get("cache") 1258 if cache is None: 1259 cache_str = "" 1260 elif cache is True: 1261 cache_str = " CACHE" 1262 else: 1263 cache_str = f" CACHE {cache}" 1264 1265 options = self.expressions(expression, key="options", flat=True, sep=" ") 1266 options = f" {options}" if options else "" 1267 1268 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1269 1270 def clone_sql(self, expression: exp.Clone) -> str: 1271 this = self.sql(expression, "this") 1272 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1273 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1274 return f"{shallow}{keyword} {this}" 1275 1276 def describe_sql(self, expression: exp.Describe) -> str: 1277 style = expression.args.get("style") 1278 style = f" {style}" if style else "" 1279 partition = self.sql(expression, "partition") 1280 partition = f" {partition}" if partition else "" 1281 format = self.sql(expression, "format") 1282 format = f" {format}" if format else "" 1283 1284 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1285 1286 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1287 tag = self.sql(expression, "tag") 1288 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1289 1290 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1291 with_ = self.sql(expression, "with") 1292 if with_: 1293 sql = f"{with_}{self.sep()}{sql}" 1294 return sql 1295 1296 def with_sql(self, expression: exp.With) -> str: 1297 sql = self.expressions(expression, flat=True) 1298 recursive = ( 1299 "RECURSIVE " 1300 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1301 else "" 1302 ) 1303 search = self.sql(expression, "search") 1304 search = f" {search}" if search else "" 1305 1306 return f"WITH {recursive}{sql}{search}" 1307 1308 def cte_sql(self, expression: exp.CTE) -> str: 1309 alias = expression.args.get("alias") 1310 if alias: 1311 alias.add_comments(expression.pop_comments()) 1312 1313 alias_sql = self.sql(expression, "alias") 1314 1315 materialized = expression.args.get("materialized") 1316 if materialized is False: 1317 materialized = "NOT MATERIALIZED " 1318 elif materialized: 1319 materialized = "MATERIALIZED " 1320 1321 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1322 1323 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1324 alias = self.sql(expression, "this") 1325 columns = self.expressions(expression, key="columns", flat=True) 1326 columns = f"({columns})" if columns else "" 1327 1328 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1329 columns = "" 1330 self.unsupported("Named columns are not supported in table alias.") 1331 1332 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1333 alias = self._next_name() 1334 1335 return f"{alias}{columns}" 1336 1337 def bitstring_sql(self, expression: exp.BitString) -> str: 1338 this = self.sql(expression, "this") 1339 if self.dialect.BIT_START: 1340 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1341 return f"{int(this, 2)}" 1342 1343 def hexstring_sql( 1344 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1345 ) -> str: 1346 this = self.sql(expression, "this") 1347 is_integer_type = expression.args.get("is_integer") 1348 1349 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1350 not self.dialect.HEX_START and not binary_function_repr 1351 ): 1352 # Integer representation will be returned if: 1353 # - The read dialect treats the hex value as integer literal but not the write 1354 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1355 return f"{int(this, 16)}" 1356 1357 if not is_integer_type: 1358 # Read dialect treats the hex value as BINARY/BLOB 1359 if binary_function_repr: 1360 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1361 return self.func(binary_function_repr, exp.Literal.string(this)) 1362 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1363 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1364 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1365 1366 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1367 1368 def bytestring_sql(self, expression: exp.ByteString) -> str: 1369 this = self.sql(expression, "this") 1370 if self.dialect.BYTE_START: 1371 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1372 return this 1373 1374 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1375 this = self.sql(expression, "this") 1376 escape = expression.args.get("escape") 1377 1378 if self.dialect.UNICODE_START: 1379 escape_substitute = r"\\\1" 1380 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1381 else: 1382 escape_substitute = r"\\u\1" 1383 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1384 1385 if escape: 1386 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1387 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1388 else: 1389 escape_pattern = ESCAPED_UNICODE_RE 1390 escape_sql = "" 1391 1392 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1393 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1394 1395 return f"{left_quote}{this}{right_quote}{escape_sql}" 1396 1397 def rawstring_sql(self, expression: exp.RawString) -> str: 1398 string = expression.this 1399 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1400 string = string.replace("\\", "\\\\") 1401 1402 string = self.escape_str(string, escape_backslash=False) 1403 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1404 1405 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1406 this = self.sql(expression, "this") 1407 specifier = self.sql(expression, "expression") 1408 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1409 return f"{this}{specifier}" 1410 1411 def datatype_sql(self, expression: exp.DataType) -> str: 1412 nested = "" 1413 values = "" 1414 interior = self.expressions(expression, flat=True) 1415 1416 type_value = expression.this 1417 if type_value in self.UNSUPPORTED_TYPES: 1418 self.unsupported( 1419 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1420 ) 1421 1422 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1423 type_sql = self.sql(expression, "kind") 1424 else: 1425 type_sql = ( 1426 self.TYPE_MAPPING.get(type_value, type_value.value) 1427 if isinstance(type_value, exp.DataType.Type) 1428 else type_value 1429 ) 1430 1431 if interior: 1432 if expression.args.get("nested"): 1433 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1434 if expression.args.get("values") is not None: 1435 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1436 values = self.expressions(expression, key="values", flat=True) 1437 values = f"{delimiters[0]}{values}{delimiters[1]}" 1438 elif type_value == exp.DataType.Type.INTERVAL: 1439 nested = f" {interior}" 1440 else: 1441 nested = f"({interior})" 1442 1443 type_sql = f"{type_sql}{nested}{values}" 1444 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1445 exp.DataType.Type.TIMETZ, 1446 exp.DataType.Type.TIMESTAMPTZ, 1447 ): 1448 type_sql = f"{type_sql} WITH TIME ZONE" 1449 1450 return type_sql 1451 1452 def directory_sql(self, expression: exp.Directory) -> str: 1453 local = "LOCAL " if expression.args.get("local") else "" 1454 row_format = self.sql(expression, "row_format") 1455 row_format = f" {row_format}" if row_format else "" 1456 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1457 1458 def delete_sql(self, expression: exp.Delete) -> str: 1459 this = self.sql(expression, "this") 1460 this = f" FROM {this}" if this else "" 1461 using = self.sql(expression, "using") 1462 using = f" USING {using}" if using else "" 1463 cluster = self.sql(expression, "cluster") 1464 cluster = f" {cluster}" if cluster else "" 1465 where = self.sql(expression, "where") 1466 returning = self.sql(expression, "returning") 1467 limit = self.sql(expression, "limit") 1468 tables = self.expressions(expression, key="tables") 1469 tables = f" {tables}" if tables else "" 1470 if self.RETURNING_END: 1471 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1472 else: 1473 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1474 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1475 1476 def drop_sql(self, expression: exp.Drop) -> str: 1477 this = self.sql(expression, "this") 1478 expressions = self.expressions(expression, flat=True) 1479 expressions = f" ({expressions})" if expressions else "" 1480 kind = expression.args["kind"] 1481 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1482 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1483 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1484 on_cluster = self.sql(expression, "cluster") 1485 on_cluster = f" {on_cluster}" if on_cluster else "" 1486 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1487 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1488 cascade = " CASCADE" if expression.args.get("cascade") else "" 1489 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1490 purge = " PURGE" if expression.args.get("purge") else "" 1491 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1492 1493 def set_operation(self, expression: exp.SetOperation) -> str: 1494 op_type = type(expression) 1495 op_name = op_type.key.upper() 1496 1497 distinct = expression.args.get("distinct") 1498 if ( 1499 distinct is False 1500 and op_type in (exp.Except, exp.Intersect) 1501 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1502 ): 1503 self.unsupported(f"{op_name} ALL is not supported") 1504 1505 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1506 1507 if distinct is None: 1508 distinct = default_distinct 1509 if distinct is None: 1510 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1511 1512 if distinct is default_distinct: 1513 distinct_or_all = "" 1514 else: 1515 distinct_or_all = " DISTINCT" if distinct else " ALL" 1516 1517 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1518 side_kind = f"{side_kind} " if side_kind else "" 1519 1520 by_name = " BY NAME" if expression.args.get("by_name") else "" 1521 on = self.expressions(expression, key="on", flat=True) 1522 on = f" ON ({on})" if on else "" 1523 1524 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1525 1526 def set_operations(self, expression: exp.SetOperation) -> str: 1527 if not self.SET_OP_MODIFIERS: 1528 limit = expression.args.get("limit") 1529 order = expression.args.get("order") 1530 1531 if limit or order: 1532 select = self._move_ctes_to_top_level( 1533 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1534 ) 1535 1536 if limit: 1537 select = select.limit(limit.pop(), copy=False) 1538 if order: 1539 select = select.order_by(order.pop(), copy=False) 1540 return self.sql(select) 1541 1542 sqls: t.List[str] = [] 1543 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1544 1545 while stack: 1546 node = stack.pop() 1547 1548 if isinstance(node, exp.SetOperation): 1549 stack.append(node.expression) 1550 stack.append( 1551 self.maybe_comment( 1552 self.set_operation(node), comments=node.comments, separated=True 1553 ) 1554 ) 1555 stack.append(node.this) 1556 else: 1557 sqls.append(self.sql(node)) 1558 1559 this = self.sep().join(sqls) 1560 this = self.query_modifiers(expression, this) 1561 return self.prepend_ctes(expression, this) 1562 1563 def fetch_sql(self, expression: exp.Fetch) -> str: 1564 direction = expression.args.get("direction") 1565 direction = f" {direction}" if direction else "" 1566 count = self.sql(expression, "count") 1567 count = f" {count}" if count else "" 1568 limit_options = self.sql(expression, "limit_options") 1569 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1570 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1571 1572 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1573 percent = " PERCENT" if expression.args.get("percent") else "" 1574 rows = " ROWS" if expression.args.get("rows") else "" 1575 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1576 if not with_ties and rows: 1577 with_ties = " ONLY" 1578 return f"{percent}{rows}{with_ties}" 1579 1580 def filter_sql(self, expression: exp.Filter) -> str: 1581 if self.AGGREGATE_FILTER_SUPPORTED: 1582 this = self.sql(expression, "this") 1583 where = self.sql(expression, "expression").strip() 1584 return f"{this} FILTER({where})" 1585 1586 agg = expression.this 1587 agg_arg = agg.this 1588 cond = expression.expression.this 1589 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1590 return self.sql(agg) 1591 1592 def hint_sql(self, expression: exp.Hint) -> str: 1593 if not self.QUERY_HINTS: 1594 self.unsupported("Hints are not supported") 1595 return "" 1596 1597 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1598 1599 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1600 using = self.sql(expression, "using") 1601 using = f" USING {using}" if using else "" 1602 columns = self.expressions(expression, key="columns", flat=True) 1603 columns = f"({columns})" if columns else "" 1604 partition_by = self.expressions(expression, key="partition_by", flat=True) 1605 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1606 where = self.sql(expression, "where") 1607 include = self.expressions(expression, key="include", flat=True) 1608 if include: 1609 include = f" INCLUDE ({include})" 1610 with_storage = self.expressions(expression, key="with_storage", flat=True) 1611 with_storage = f" WITH ({with_storage})" if with_storage else "" 1612 tablespace = self.sql(expression, "tablespace") 1613 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1614 on = self.sql(expression, "on") 1615 on = f" ON {on}" if on else "" 1616 1617 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1618 1619 def index_sql(self, expression: exp.Index) -> str: 1620 unique = "UNIQUE " if expression.args.get("unique") else "" 1621 primary = "PRIMARY " if expression.args.get("primary") else "" 1622 amp = "AMP " if expression.args.get("amp") else "" 1623 name = self.sql(expression, "this") 1624 name = f"{name} " if name else "" 1625 table = self.sql(expression, "table") 1626 table = f"{self.INDEX_ON} {table}" if table else "" 1627 1628 index = "INDEX " if not table else "" 1629 1630 params = self.sql(expression, "params") 1631 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1632 1633 def identifier_sql(self, expression: exp.Identifier) -> str: 1634 text = expression.name 1635 lower = text.lower() 1636 text = lower if self.normalize and not expression.quoted else text 1637 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1638 if ( 1639 expression.quoted 1640 or self.dialect.can_identify(text, self.identify) 1641 or lower in self.RESERVED_KEYWORDS 1642 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1643 ): 1644 text = f"{self._identifier_start}{text}{self._identifier_end}" 1645 return text 1646 1647 def hex_sql(self, expression: exp.Hex) -> str: 1648 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1649 if self.dialect.HEX_LOWERCASE: 1650 text = self.func("LOWER", text) 1651 1652 return text 1653 1654 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1655 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1656 if not self.dialect.HEX_LOWERCASE: 1657 text = self.func("LOWER", text) 1658 return text 1659 1660 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1661 input_format = self.sql(expression, "input_format") 1662 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1663 output_format = self.sql(expression, "output_format") 1664 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1665 return self.sep().join((input_format, output_format)) 1666 1667 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1668 string = self.sql(exp.Literal.string(expression.name)) 1669 return f"{prefix}{string}" 1670 1671 def partition_sql(self, expression: exp.Partition) -> str: 1672 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1673 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1674 1675 def properties_sql(self, expression: exp.Properties) -> str: 1676 root_properties = [] 1677 with_properties = [] 1678 1679 for p in expression.expressions: 1680 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1681 if p_loc == exp.Properties.Location.POST_WITH: 1682 with_properties.append(p) 1683 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1684 root_properties.append(p) 1685 1686 root_props_ast = exp.Properties(expressions=root_properties) 1687 root_props_ast.parent = expression.parent 1688 1689 with_props_ast = exp.Properties(expressions=with_properties) 1690 with_props_ast.parent = expression.parent 1691 1692 root_props = self.root_properties(root_props_ast) 1693 with_props = self.with_properties(with_props_ast) 1694 1695 if root_props and with_props and not self.pretty: 1696 with_props = " " + with_props 1697 1698 return root_props + with_props 1699 1700 def root_properties(self, properties: exp.Properties) -> str: 1701 if properties.expressions: 1702 return self.expressions(properties, indent=False, sep=" ") 1703 return "" 1704 1705 def properties( 1706 self, 1707 properties: exp.Properties, 1708 prefix: str = "", 1709 sep: str = ", ", 1710 suffix: str = "", 1711 wrapped: bool = True, 1712 ) -> str: 1713 if properties.expressions: 1714 expressions = self.expressions(properties, sep=sep, indent=False) 1715 if expressions: 1716 expressions = self.wrap(expressions) if wrapped else expressions 1717 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1718 return "" 1719 1720 def with_properties(self, properties: exp.Properties) -> str: 1721 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1722 1723 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1724 properties_locs = defaultdict(list) 1725 for p in properties.expressions: 1726 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1727 if p_loc != exp.Properties.Location.UNSUPPORTED: 1728 properties_locs[p_loc].append(p) 1729 else: 1730 self.unsupported(f"Unsupported property {p.key}") 1731 1732 return properties_locs 1733 1734 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1735 if isinstance(expression.this, exp.Dot): 1736 return self.sql(expression, "this") 1737 return f"'{expression.name}'" if string_key else expression.name 1738 1739 def property_sql(self, expression: exp.Property) -> str: 1740 property_cls = expression.__class__ 1741 if property_cls == exp.Property: 1742 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1743 1744 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1745 if not property_name: 1746 self.unsupported(f"Unsupported property {expression.key}") 1747 1748 return f"{property_name}={self.sql(expression, 'this')}" 1749 1750 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1751 if self.SUPPORTS_CREATE_TABLE_LIKE: 1752 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1753 options = f" {options}" if options else "" 1754 1755 like = f"LIKE {self.sql(expression, 'this')}{options}" 1756 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1757 like = f"({like})" 1758 1759 return like 1760 1761 if expression.expressions: 1762 self.unsupported("Transpilation of LIKE property options is unsupported") 1763 1764 select = exp.select("*").from_(expression.this).limit(0) 1765 return f"AS {self.sql(select)}" 1766 1767 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1768 no = "NO " if expression.args.get("no") else "" 1769 protection = " PROTECTION" if expression.args.get("protection") else "" 1770 return f"{no}FALLBACK{protection}" 1771 1772 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1773 no = "NO " if expression.args.get("no") else "" 1774 local = expression.args.get("local") 1775 local = f"{local} " if local else "" 1776 dual = "DUAL " if expression.args.get("dual") else "" 1777 before = "BEFORE " if expression.args.get("before") else "" 1778 after = "AFTER " if expression.args.get("after") else "" 1779 return f"{no}{local}{dual}{before}{after}JOURNAL" 1780 1781 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1782 freespace = self.sql(expression, "this") 1783 percent = " PERCENT" if expression.args.get("percent") else "" 1784 return f"FREESPACE={freespace}{percent}" 1785 1786 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1787 if expression.args.get("default"): 1788 property = "DEFAULT" 1789 elif expression.args.get("on"): 1790 property = "ON" 1791 else: 1792 property = "OFF" 1793 return f"CHECKSUM={property}" 1794 1795 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1796 if expression.args.get("no"): 1797 return "NO MERGEBLOCKRATIO" 1798 if expression.args.get("default"): 1799 return "DEFAULT MERGEBLOCKRATIO" 1800 1801 percent = " PERCENT" if expression.args.get("percent") else "" 1802 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1803 1804 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1805 default = expression.args.get("default") 1806 minimum = expression.args.get("minimum") 1807 maximum = expression.args.get("maximum") 1808 if default or minimum or maximum: 1809 if default: 1810 prop = "DEFAULT" 1811 elif minimum: 1812 prop = "MINIMUM" 1813 else: 1814 prop = "MAXIMUM" 1815 return f"{prop} DATABLOCKSIZE" 1816 units = expression.args.get("units") 1817 units = f" {units}" if units else "" 1818 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1819 1820 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1821 autotemp = expression.args.get("autotemp") 1822 always = expression.args.get("always") 1823 default = expression.args.get("default") 1824 manual = expression.args.get("manual") 1825 never = expression.args.get("never") 1826 1827 if autotemp is not None: 1828 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1829 elif always: 1830 prop = "ALWAYS" 1831 elif default: 1832 prop = "DEFAULT" 1833 elif manual: 1834 prop = "MANUAL" 1835 elif never: 1836 prop = "NEVER" 1837 return f"BLOCKCOMPRESSION={prop}" 1838 1839 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1840 no = expression.args.get("no") 1841 no = " NO" if no else "" 1842 concurrent = expression.args.get("concurrent") 1843 concurrent = " CONCURRENT" if concurrent else "" 1844 target = self.sql(expression, "target") 1845 target = f" {target}" if target else "" 1846 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1847 1848 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1849 if isinstance(expression.this, list): 1850 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1851 if expression.this: 1852 modulus = self.sql(expression, "this") 1853 remainder = self.sql(expression, "expression") 1854 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1855 1856 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1857 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1858 return f"FROM ({from_expressions}) TO ({to_expressions})" 1859 1860 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1861 this = self.sql(expression, "this") 1862 1863 for_values_or_default = expression.expression 1864 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1865 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1866 else: 1867 for_values_or_default = " DEFAULT" 1868 1869 return f"PARTITION OF {this}{for_values_or_default}" 1870 1871 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1872 kind = expression.args.get("kind") 1873 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1874 for_or_in = expression.args.get("for_or_in") 1875 for_or_in = f" {for_or_in}" if for_or_in else "" 1876 lock_type = expression.args.get("lock_type") 1877 override = " OVERRIDE" if expression.args.get("override") else "" 1878 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1879 1880 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1881 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1882 statistics = expression.args.get("statistics") 1883 statistics_sql = "" 1884 if statistics is not None: 1885 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1886 return f"{data_sql}{statistics_sql}" 1887 1888 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1889 this = self.sql(expression, "this") 1890 this = f"HISTORY_TABLE={this}" if this else "" 1891 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1892 data_consistency = ( 1893 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1894 ) 1895 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1896 retention_period = ( 1897 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1898 ) 1899 1900 if this: 1901 on_sql = self.func("ON", this, data_consistency, retention_period) 1902 else: 1903 on_sql = "ON" if expression.args.get("on") else "OFF" 1904 1905 sql = f"SYSTEM_VERSIONING={on_sql}" 1906 1907 return f"WITH({sql})" if expression.args.get("with") else sql 1908 1909 def insert_sql(self, expression: exp.Insert) -> str: 1910 hint = self.sql(expression, "hint") 1911 overwrite = expression.args.get("overwrite") 1912 1913 if isinstance(expression.this, exp.Directory): 1914 this = " OVERWRITE" if overwrite else " INTO" 1915 else: 1916 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1917 1918 stored = self.sql(expression, "stored") 1919 stored = f" {stored}" if stored else "" 1920 alternative = expression.args.get("alternative") 1921 alternative = f" OR {alternative}" if alternative else "" 1922 ignore = " IGNORE" if expression.args.get("ignore") else "" 1923 is_function = expression.args.get("is_function") 1924 if is_function: 1925 this = f"{this} FUNCTION" 1926 this = f"{this} {self.sql(expression, 'this')}" 1927 1928 exists = " IF EXISTS" if expression.args.get("exists") else "" 1929 where = self.sql(expression, "where") 1930 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1931 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1932 on_conflict = self.sql(expression, "conflict") 1933 on_conflict = f" {on_conflict}" if on_conflict else "" 1934 by_name = " BY NAME" if expression.args.get("by_name") else "" 1935 returning = self.sql(expression, "returning") 1936 1937 if self.RETURNING_END: 1938 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1939 else: 1940 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1941 1942 partition_by = self.sql(expression, "partition") 1943 partition_by = f" {partition_by}" if partition_by else "" 1944 settings = self.sql(expression, "settings") 1945 settings = f" {settings}" if settings else "" 1946 1947 source = self.sql(expression, "source") 1948 source = f"TABLE {source}" if source else "" 1949 1950 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1951 return self.prepend_ctes(expression, sql) 1952 1953 def introducer_sql(self, expression: exp.Introducer) -> str: 1954 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1955 1956 def kill_sql(self, expression: exp.Kill) -> str: 1957 kind = self.sql(expression, "kind") 1958 kind = f" {kind}" if kind else "" 1959 this = self.sql(expression, "this") 1960 this = f" {this}" if this else "" 1961 return f"KILL{kind}{this}" 1962 1963 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1964 return expression.name 1965 1966 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1967 return expression.name 1968 1969 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1970 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1971 1972 constraint = self.sql(expression, "constraint") 1973 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1974 1975 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1976 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1977 action = self.sql(expression, "action") 1978 1979 expressions = self.expressions(expression, flat=True) 1980 if expressions: 1981 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1982 expressions = f" {set_keyword}{expressions}" 1983 1984 where = self.sql(expression, "where") 1985 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1986 1987 def returning_sql(self, expression: exp.Returning) -> str: 1988 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1989 1990 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1991 fields = self.sql(expression, "fields") 1992 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1993 escaped = self.sql(expression, "escaped") 1994 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1995 items = self.sql(expression, "collection_items") 1996 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1997 keys = self.sql(expression, "map_keys") 1998 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1999 lines = self.sql(expression, "lines") 2000 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2001 null = self.sql(expression, "null") 2002 null = f" NULL DEFINED AS {null}" if null else "" 2003 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2004 2005 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2006 return f"WITH ({self.expressions(expression, flat=True)})" 2007 2008 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2009 this = f"{self.sql(expression, 'this')} INDEX" 2010 target = self.sql(expression, "target") 2011 target = f" FOR {target}" if target else "" 2012 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2013 2014 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2015 this = self.sql(expression, "this") 2016 kind = self.sql(expression, "kind") 2017 expr = self.sql(expression, "expression") 2018 return f"{this} ({kind} => {expr})" 2019 2020 def table_parts(self, expression: exp.Table) -> str: 2021 return ".".join( 2022 self.sql(part) 2023 for part in ( 2024 expression.args.get("catalog"), 2025 expression.args.get("db"), 2026 expression.args.get("this"), 2027 ) 2028 if part is not None 2029 ) 2030 2031 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2032 table = self.table_parts(expression) 2033 only = "ONLY " if expression.args.get("only") else "" 2034 partition = self.sql(expression, "partition") 2035 partition = f" {partition}" if partition else "" 2036 version = self.sql(expression, "version") 2037 version = f" {version}" if version else "" 2038 alias = self.sql(expression, "alias") 2039 alias = f"{sep}{alias}" if alias else "" 2040 2041 sample = self.sql(expression, "sample") 2042 if self.dialect.ALIAS_POST_TABLESAMPLE: 2043 sample_pre_alias = sample 2044 sample_post_alias = "" 2045 else: 2046 sample_pre_alias = "" 2047 sample_post_alias = sample 2048 2049 hints = self.expressions(expression, key="hints", sep=" ") 2050 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2051 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2052 joins = self.indent( 2053 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2054 ) 2055 laterals = self.expressions(expression, key="laterals", sep="") 2056 2057 file_format = self.sql(expression, "format") 2058 if file_format: 2059 pattern = self.sql(expression, "pattern") 2060 pattern = f", PATTERN => {pattern}" if pattern else "" 2061 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2062 2063 ordinality = expression.args.get("ordinality") or "" 2064 if ordinality: 2065 ordinality = f" WITH ORDINALITY{alias}" 2066 alias = "" 2067 2068 when = self.sql(expression, "when") 2069 if when: 2070 table = f"{table} {when}" 2071 2072 changes = self.sql(expression, "changes") 2073 changes = f" {changes}" if changes else "" 2074 2075 rows_from = self.expressions(expression, key="rows_from") 2076 if rows_from: 2077 table = f"ROWS FROM {self.wrap(rows_from)}" 2078 2079 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2080 2081 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2082 table = self.func("TABLE", expression.this) 2083 alias = self.sql(expression, "alias") 2084 alias = f" AS {alias}" if alias else "" 2085 sample = self.sql(expression, "sample") 2086 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2087 joins = self.indent( 2088 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2089 ) 2090 return f"{table}{alias}{pivots}{sample}{joins}" 2091 2092 def tablesample_sql( 2093 self, 2094 expression: exp.TableSample, 2095 tablesample_keyword: t.Optional[str] = None, 2096 ) -> str: 2097 method = self.sql(expression, "method") 2098 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2099 numerator = self.sql(expression, "bucket_numerator") 2100 denominator = self.sql(expression, "bucket_denominator") 2101 field = self.sql(expression, "bucket_field") 2102 field = f" ON {field}" if field else "" 2103 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2104 seed = self.sql(expression, "seed") 2105 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2106 2107 size = self.sql(expression, "size") 2108 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2109 size = f"{size} ROWS" 2110 2111 percent = self.sql(expression, "percent") 2112 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2113 percent = f"{percent} PERCENT" 2114 2115 expr = f"{bucket}{percent}{size}" 2116 if self.TABLESAMPLE_REQUIRES_PARENS: 2117 expr = f"({expr})" 2118 2119 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2120 2121 def pivot_sql(self, expression: exp.Pivot) -> str: 2122 expressions = self.expressions(expression, flat=True) 2123 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2124 2125 group = self.sql(expression, "group") 2126 2127 if expression.this: 2128 this = self.sql(expression, "this") 2129 if not expressions: 2130 return f"UNPIVOT {this}" 2131 2132 on = f"{self.seg('ON')} {expressions}" 2133 into = self.sql(expression, "into") 2134 into = f"{self.seg('INTO')} {into}" if into else "" 2135 using = self.expressions(expression, key="using", flat=True) 2136 using = f"{self.seg('USING')} {using}" if using else "" 2137 return f"{direction} {this}{on}{into}{using}{group}" 2138 2139 alias = self.sql(expression, "alias") 2140 alias = f" AS {alias}" if alias else "" 2141 2142 fields = self.expressions( 2143 expression, 2144 "fields", 2145 sep=" ", 2146 dynamic=True, 2147 new_line=True, 2148 skip_first=True, 2149 skip_last=True, 2150 ) 2151 2152 include_nulls = expression.args.get("include_nulls") 2153 if include_nulls is not None: 2154 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2155 else: 2156 nulls = "" 2157 2158 default_on_null = self.sql(expression, "default_on_null") 2159 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2160 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2161 2162 def version_sql(self, expression: exp.Version) -> str: 2163 this = f"FOR {expression.name}" 2164 kind = expression.text("kind") 2165 expr = self.sql(expression, "expression") 2166 return f"{this} {kind} {expr}" 2167 2168 def tuple_sql(self, expression: exp.Tuple) -> str: 2169 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2170 2171 def update_sql(self, expression: exp.Update) -> str: 2172 this = self.sql(expression, "this") 2173 set_sql = self.expressions(expression, flat=True) 2174 from_sql = self.sql(expression, "from") 2175 where_sql = self.sql(expression, "where") 2176 returning = self.sql(expression, "returning") 2177 order = self.sql(expression, "order") 2178 limit = self.sql(expression, "limit") 2179 if self.RETURNING_END: 2180 expression_sql = f"{from_sql}{where_sql}{returning}" 2181 else: 2182 expression_sql = f"{returning}{from_sql}{where_sql}" 2183 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2184 return self.prepend_ctes(expression, sql) 2185 2186 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2187 values_as_table = values_as_table and self.VALUES_AS_TABLE 2188 2189 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2190 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2191 args = self.expressions(expression) 2192 alias = self.sql(expression, "alias") 2193 values = f"VALUES{self.seg('')}{args}" 2194 values = ( 2195 f"({values})" 2196 if self.WRAP_DERIVED_VALUES 2197 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2198 else values 2199 ) 2200 return f"{values} AS {alias}" if alias else values 2201 2202 # Converts `VALUES...` expression into a series of select unions. 2203 alias_node = expression.args.get("alias") 2204 column_names = alias_node and alias_node.columns 2205 2206 selects: t.List[exp.Query] = [] 2207 2208 for i, tup in enumerate(expression.expressions): 2209 row = tup.expressions 2210 2211 if i == 0 and column_names: 2212 row = [ 2213 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2214 ] 2215 2216 selects.append(exp.Select(expressions=row)) 2217 2218 if self.pretty: 2219 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2220 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2221 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2222 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2223 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2224 2225 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2226 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2227 return f"({unions}){alias}" 2228 2229 def var_sql(self, expression: exp.Var) -> str: 2230 return self.sql(expression, "this") 2231 2232 @unsupported_args("expressions") 2233 def into_sql(self, expression: exp.Into) -> str: 2234 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2235 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2236 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2237 2238 def from_sql(self, expression: exp.From) -> str: 2239 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2240 2241 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2242 grouping_sets = self.expressions(expression, indent=False) 2243 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2244 2245 def rollup_sql(self, expression: exp.Rollup) -> str: 2246 expressions = self.expressions(expression, indent=False) 2247 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2248 2249 def cube_sql(self, expression: exp.Cube) -> str: 2250 expressions = self.expressions(expression, indent=False) 2251 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2252 2253 def group_sql(self, expression: exp.Group) -> str: 2254 group_by_all = expression.args.get("all") 2255 if group_by_all is True: 2256 modifier = " ALL" 2257 elif group_by_all is False: 2258 modifier = " DISTINCT" 2259 else: 2260 modifier = "" 2261 2262 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2263 2264 grouping_sets = self.expressions(expression, key="grouping_sets") 2265 cube = self.expressions(expression, key="cube") 2266 rollup = self.expressions(expression, key="rollup") 2267 2268 groupings = csv( 2269 self.seg(grouping_sets) if grouping_sets else "", 2270 self.seg(cube) if cube else "", 2271 self.seg(rollup) if rollup else "", 2272 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2273 sep=self.GROUPINGS_SEP, 2274 ) 2275 2276 if ( 2277 expression.expressions 2278 and groupings 2279 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2280 ): 2281 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2282 2283 return f"{group_by}{groupings}" 2284 2285 def having_sql(self, expression: exp.Having) -> str: 2286 this = self.indent(self.sql(expression, "this")) 2287 return f"{self.seg('HAVING')}{self.sep()}{this}" 2288 2289 def connect_sql(self, expression: exp.Connect) -> str: 2290 start = self.sql(expression, "start") 2291 start = self.seg(f"START WITH {start}") if start else "" 2292 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2293 connect = self.sql(expression, "connect") 2294 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2295 return start + connect 2296 2297 def prior_sql(self, expression: exp.Prior) -> str: 2298 return f"PRIOR {self.sql(expression, 'this')}" 2299 2300 def join_sql(self, expression: exp.Join) -> str: 2301 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2302 side = None 2303 else: 2304 side = expression.side 2305 2306 op_sql = " ".join( 2307 op 2308 for op in ( 2309 expression.method, 2310 "GLOBAL" if expression.args.get("global") else None, 2311 side, 2312 expression.kind, 2313 expression.hint if self.JOIN_HINTS else None, 2314 ) 2315 if op 2316 ) 2317 match_cond = self.sql(expression, "match_condition") 2318 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2319 on_sql = self.sql(expression, "on") 2320 using = expression.args.get("using") 2321 2322 if not on_sql and using: 2323 on_sql = csv(*(self.sql(column) for column in using)) 2324 2325 this = expression.this 2326 this_sql = self.sql(this) 2327 2328 exprs = self.expressions(expression) 2329 if exprs: 2330 this_sql = f"{this_sql},{self.seg(exprs)}" 2331 2332 if on_sql: 2333 on_sql = self.indent(on_sql, skip_first=True) 2334 space = self.seg(" " * self.pad) if self.pretty else " " 2335 if using: 2336 on_sql = f"{space}USING ({on_sql})" 2337 else: 2338 on_sql = f"{space}ON {on_sql}" 2339 elif not op_sql: 2340 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2341 return f" {this_sql}" 2342 2343 return f", {this_sql}" 2344 2345 if op_sql != "STRAIGHT_JOIN": 2346 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2347 2348 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2349 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2350 2351 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2352 args = self.expressions(expression, flat=True) 2353 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2354 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2355 2356 def lateral_op(self, expression: exp.Lateral) -> str: 2357 cross_apply = expression.args.get("cross_apply") 2358 2359 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2360 if cross_apply is True: 2361 op = "INNER JOIN " 2362 elif cross_apply is False: 2363 op = "LEFT JOIN " 2364 else: 2365 op = "" 2366 2367 return f"{op}LATERAL" 2368 2369 def lateral_sql(self, expression: exp.Lateral) -> str: 2370 this = self.sql(expression, "this") 2371 2372 if expression.args.get("view"): 2373 alias = expression.args["alias"] 2374 columns = self.expressions(alias, key="columns", flat=True) 2375 table = f" {alias.name}" if alias.name else "" 2376 columns = f" AS {columns}" if columns else "" 2377 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2378 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2379 2380 alias = self.sql(expression, "alias") 2381 alias = f" AS {alias}" if alias else "" 2382 2383 ordinality = expression.args.get("ordinality") or "" 2384 if ordinality: 2385 ordinality = f" WITH ORDINALITY{alias}" 2386 alias = "" 2387 2388 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2389 2390 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2391 this = self.sql(expression, "this") 2392 2393 args = [ 2394 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2395 for e in (expression.args.get(k) for k in ("offset", "expression")) 2396 if e 2397 ] 2398 2399 args_sql = ", ".join(self.sql(e) for e in args) 2400 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2401 expressions = self.expressions(expression, flat=True) 2402 limit_options = self.sql(expression, "limit_options") 2403 expressions = f" BY {expressions}" if expressions else "" 2404 2405 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2406 2407 def offset_sql(self, expression: exp.Offset) -> str: 2408 this = self.sql(expression, "this") 2409 value = expression.expression 2410 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2411 expressions = self.expressions(expression, flat=True) 2412 expressions = f" BY {expressions}" if expressions else "" 2413 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2414 2415 def setitem_sql(self, expression: exp.SetItem) -> str: 2416 kind = self.sql(expression, "kind") 2417 kind = f"{kind} " if kind else "" 2418 this = self.sql(expression, "this") 2419 expressions = self.expressions(expression) 2420 collate = self.sql(expression, "collate") 2421 collate = f" COLLATE {collate}" if collate else "" 2422 global_ = "GLOBAL " if expression.args.get("global") else "" 2423 return f"{global_}{kind}{this}{expressions}{collate}" 2424 2425 def set_sql(self, expression: exp.Set) -> str: 2426 expressions = f" {self.expressions(expression, flat=True)}" 2427 tag = " TAG" if expression.args.get("tag") else "" 2428 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2429 2430 def queryband_sql(self, expression: exp.QueryBand) -> str: 2431 this = self.sql(expression, "this") 2432 update = " UPDATE" if expression.args.get("update") else "" 2433 scope = self.sql(expression, "scope") 2434 scope = f" FOR {scope}" if scope else "" 2435 2436 return f"QUERY_BAND = {this}{update}{scope}" 2437 2438 def pragma_sql(self, expression: exp.Pragma) -> str: 2439 return f"PRAGMA {self.sql(expression, 'this')}" 2440 2441 def lock_sql(self, expression: exp.Lock) -> str: 2442 if not self.LOCKING_READS_SUPPORTED: 2443 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2444 return "" 2445 2446 update = expression.args["update"] 2447 key = expression.args.get("key") 2448 if update: 2449 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2450 else: 2451 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2452 expressions = self.expressions(expression, flat=True) 2453 expressions = f" OF {expressions}" if expressions else "" 2454 wait = expression.args.get("wait") 2455 2456 if wait is not None: 2457 if isinstance(wait, exp.Literal): 2458 wait = f" WAIT {self.sql(wait)}" 2459 else: 2460 wait = " NOWAIT" if wait else " SKIP LOCKED" 2461 2462 return f"{lock_type}{expressions}{wait or ''}" 2463 2464 def literal_sql(self, expression: exp.Literal) -> str: 2465 text = expression.this or "" 2466 if expression.is_string: 2467 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2468 return text 2469 2470 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2471 if self.dialect.ESCAPED_SEQUENCES: 2472 to_escaped = self.dialect.ESCAPED_SEQUENCES 2473 text = "".join( 2474 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2475 ) 2476 2477 return self._replace_line_breaks(text).replace( 2478 self.dialect.QUOTE_END, self._escaped_quote_end 2479 ) 2480 2481 def loaddata_sql(self, expression: exp.LoadData) -> str: 2482 local = " LOCAL" if expression.args.get("local") else "" 2483 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2484 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2485 this = f" INTO TABLE {self.sql(expression, 'this')}" 2486 partition = self.sql(expression, "partition") 2487 partition = f" {partition}" if partition else "" 2488 input_format = self.sql(expression, "input_format") 2489 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2490 serde = self.sql(expression, "serde") 2491 serde = f" SERDE {serde}" if serde else "" 2492 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2493 2494 def null_sql(self, *_) -> str: 2495 return "NULL" 2496 2497 def boolean_sql(self, expression: exp.Boolean) -> str: 2498 return "TRUE" if expression.this else "FALSE" 2499 2500 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2501 this = self.sql(expression, "this") 2502 this = f"{this} " if this else this 2503 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2504 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2505 2506 def withfill_sql(self, expression: exp.WithFill) -> str: 2507 from_sql = self.sql(expression, "from") 2508 from_sql = f" FROM {from_sql}" if from_sql else "" 2509 to_sql = self.sql(expression, "to") 2510 to_sql = f" TO {to_sql}" if to_sql else "" 2511 step_sql = self.sql(expression, "step") 2512 step_sql = f" STEP {step_sql}" if step_sql else "" 2513 interpolated_values = [ 2514 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2515 if isinstance(e, exp.Alias) 2516 else self.sql(e, "this") 2517 for e in expression.args.get("interpolate") or [] 2518 ] 2519 interpolate = ( 2520 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2521 ) 2522 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2523 2524 def cluster_sql(self, expression: exp.Cluster) -> str: 2525 return self.op_expressions("CLUSTER BY", expression) 2526 2527 def distribute_sql(self, expression: exp.Distribute) -> str: 2528 return self.op_expressions("DISTRIBUTE BY", expression) 2529 2530 def sort_sql(self, expression: exp.Sort) -> str: 2531 return self.op_expressions("SORT BY", expression) 2532 2533 def ordered_sql(self, expression: exp.Ordered) -> str: 2534 desc = expression.args.get("desc") 2535 asc = not desc 2536 2537 nulls_first = expression.args.get("nulls_first") 2538 nulls_last = not nulls_first 2539 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2540 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2541 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2542 2543 this = self.sql(expression, "this") 2544 2545 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2546 nulls_sort_change = "" 2547 if nulls_first and ( 2548 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2549 ): 2550 nulls_sort_change = " NULLS FIRST" 2551 elif ( 2552 nulls_last 2553 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2554 and not nulls_are_last 2555 ): 2556 nulls_sort_change = " NULLS LAST" 2557 2558 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2559 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2560 window = expression.find_ancestor(exp.Window, exp.Select) 2561 if isinstance(window, exp.Window) and window.args.get("spec"): 2562 self.unsupported( 2563 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2564 ) 2565 nulls_sort_change = "" 2566 elif self.NULL_ORDERING_SUPPORTED is False and ( 2567 (asc and nulls_sort_change == " NULLS LAST") 2568 or (desc and nulls_sort_change == " NULLS FIRST") 2569 ): 2570 # BigQuery does not allow these ordering/nulls combinations when used under 2571 # an aggregation func or under a window containing one 2572 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2573 2574 if isinstance(ancestor, exp.Window): 2575 ancestor = ancestor.this 2576 if isinstance(ancestor, exp.AggFunc): 2577 self.unsupported( 2578 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2579 ) 2580 nulls_sort_change = "" 2581 elif self.NULL_ORDERING_SUPPORTED is None: 2582 if expression.this.is_int: 2583 self.unsupported( 2584 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2585 ) 2586 elif not isinstance(expression.this, exp.Rand): 2587 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2588 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2589 nulls_sort_change = "" 2590 2591 with_fill = self.sql(expression, "with_fill") 2592 with_fill = f" {with_fill}" if with_fill else "" 2593 2594 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2595 2596 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2597 window_frame = self.sql(expression, "window_frame") 2598 window_frame = f"{window_frame} " if window_frame else "" 2599 2600 this = self.sql(expression, "this") 2601 2602 return f"{window_frame}{this}" 2603 2604 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2605 partition = self.partition_by_sql(expression) 2606 order = self.sql(expression, "order") 2607 measures = self.expressions(expression, key="measures") 2608 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2609 rows = self.sql(expression, "rows") 2610 rows = self.seg(rows) if rows else "" 2611 after = self.sql(expression, "after") 2612 after = self.seg(after) if after else "" 2613 pattern = self.sql(expression, "pattern") 2614 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2615 definition_sqls = [ 2616 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2617 for definition in expression.args.get("define", []) 2618 ] 2619 definitions = self.expressions(sqls=definition_sqls) 2620 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2621 body = "".join( 2622 ( 2623 partition, 2624 order, 2625 measures, 2626 rows, 2627 after, 2628 pattern, 2629 define, 2630 ) 2631 ) 2632 alias = self.sql(expression, "alias") 2633 alias = f" {alias}" if alias else "" 2634 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2635 2636 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2637 limit = expression.args.get("limit") 2638 2639 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2640 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2641 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2642 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2643 2644 return csv( 2645 *sqls, 2646 *[self.sql(join) for join in expression.args.get("joins") or []], 2647 self.sql(expression, "match"), 2648 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2649 self.sql(expression, "prewhere"), 2650 self.sql(expression, "where"), 2651 self.sql(expression, "connect"), 2652 self.sql(expression, "group"), 2653 self.sql(expression, "having"), 2654 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2655 self.sql(expression, "order"), 2656 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2657 *self.after_limit_modifiers(expression), 2658 self.options_modifier(expression), 2659 self.for_modifiers(expression), 2660 sep="", 2661 ) 2662 2663 def options_modifier(self, expression: exp.Expression) -> str: 2664 options = self.expressions(expression, key="options") 2665 return f" {options}" if options else "" 2666 2667 def for_modifiers(self, expression: exp.Expression) -> str: 2668 for_modifiers = self.expressions(expression, key="for") 2669 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2670 2671 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2672 self.unsupported("Unsupported query option.") 2673 return "" 2674 2675 def offset_limit_modifiers( 2676 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2677 ) -> t.List[str]: 2678 return [ 2679 self.sql(expression, "offset") if fetch else self.sql(limit), 2680 self.sql(limit) if fetch else self.sql(expression, "offset"), 2681 ] 2682 2683 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2684 locks = self.expressions(expression, key="locks", sep=" ") 2685 locks = f" {locks}" if locks else "" 2686 return [locks, self.sql(expression, "sample")] 2687 2688 def select_sql(self, expression: exp.Select) -> str: 2689 into = expression.args.get("into") 2690 if not self.SUPPORTS_SELECT_INTO and into: 2691 into.pop() 2692 2693 hint = self.sql(expression, "hint") 2694 distinct = self.sql(expression, "distinct") 2695 distinct = f" {distinct}" if distinct else "" 2696 kind = self.sql(expression, "kind") 2697 2698 limit = expression.args.get("limit") 2699 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2700 top = self.limit_sql(limit, top=True) 2701 limit.pop() 2702 else: 2703 top = "" 2704 2705 expressions = self.expressions(expression) 2706 2707 if kind: 2708 if kind in self.SELECT_KINDS: 2709 kind = f" AS {kind}" 2710 else: 2711 if kind == "STRUCT": 2712 expressions = self.expressions( 2713 sqls=[ 2714 self.sql( 2715 exp.Struct( 2716 expressions=[ 2717 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2718 if isinstance(e, exp.Alias) 2719 else e 2720 for e in expression.expressions 2721 ] 2722 ) 2723 ) 2724 ] 2725 ) 2726 kind = "" 2727 2728 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2729 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2730 2731 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2732 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2733 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2734 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2735 sql = self.query_modifiers( 2736 expression, 2737 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2738 self.sql(expression, "into", comment=False), 2739 self.sql(expression, "from", comment=False), 2740 ) 2741 2742 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2743 if expression.args.get("with"): 2744 sql = self.maybe_comment(sql, expression) 2745 expression.pop_comments() 2746 2747 sql = self.prepend_ctes(expression, sql) 2748 2749 if not self.SUPPORTS_SELECT_INTO and into: 2750 if into.args.get("temporary"): 2751 table_kind = " TEMPORARY" 2752 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2753 table_kind = " UNLOGGED" 2754 else: 2755 table_kind = "" 2756 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2757 2758 return sql 2759 2760 def schema_sql(self, expression: exp.Schema) -> str: 2761 this = self.sql(expression, "this") 2762 sql = self.schema_columns_sql(expression) 2763 return f"{this} {sql}" if this and sql else this or sql 2764 2765 def schema_columns_sql(self, expression: exp.Schema) -> str: 2766 if expression.expressions: 2767 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2768 return "" 2769 2770 def star_sql(self, expression: exp.Star) -> str: 2771 except_ = self.expressions(expression, key="except", flat=True) 2772 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2773 replace = self.expressions(expression, key="replace", flat=True) 2774 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2775 rename = self.expressions(expression, key="rename", flat=True) 2776 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2777 return f"*{except_}{replace}{rename}" 2778 2779 def parameter_sql(self, expression: exp.Parameter) -> str: 2780 this = self.sql(expression, "this") 2781 return f"{self.PARAMETER_TOKEN}{this}" 2782 2783 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2784 this = self.sql(expression, "this") 2785 kind = expression.text("kind") 2786 if kind: 2787 kind = f"{kind}." 2788 return f"@@{kind}{this}" 2789 2790 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2791 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2792 2793 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2794 alias = self.sql(expression, "alias") 2795 alias = f"{sep}{alias}" if alias else "" 2796 sample = self.sql(expression, "sample") 2797 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2798 alias = f"{sample}{alias}" 2799 2800 # Set to None so it's not generated again by self.query_modifiers() 2801 expression.set("sample", None) 2802 2803 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2804 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2805 return self.prepend_ctes(expression, sql) 2806 2807 def qualify_sql(self, expression: exp.Qualify) -> str: 2808 this = self.indent(self.sql(expression, "this")) 2809 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2810 2811 def unnest_sql(self, expression: exp.Unnest) -> str: 2812 args = self.expressions(expression, flat=True) 2813 2814 alias = expression.args.get("alias") 2815 offset = expression.args.get("offset") 2816 2817 if self.UNNEST_WITH_ORDINALITY: 2818 if alias and isinstance(offset, exp.Expression): 2819 alias.append("columns", offset) 2820 2821 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2822 columns = alias.columns 2823 alias = self.sql(columns[0]) if columns else "" 2824 else: 2825 alias = self.sql(alias) 2826 2827 alias = f" AS {alias}" if alias else alias 2828 if self.UNNEST_WITH_ORDINALITY: 2829 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2830 else: 2831 if isinstance(offset, exp.Expression): 2832 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2833 elif offset: 2834 suffix = f"{alias} WITH OFFSET" 2835 else: 2836 suffix = alias 2837 2838 return f"UNNEST({args}){suffix}" 2839 2840 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2841 return "" 2842 2843 def where_sql(self, expression: exp.Where) -> str: 2844 this = self.indent(self.sql(expression, "this")) 2845 return f"{self.seg('WHERE')}{self.sep()}{this}" 2846 2847 def window_sql(self, expression: exp.Window) -> str: 2848 this = self.sql(expression, "this") 2849 partition = self.partition_by_sql(expression) 2850 order = expression.args.get("order") 2851 order = self.order_sql(order, flat=True) if order else "" 2852 spec = self.sql(expression, "spec") 2853 alias = self.sql(expression, "alias") 2854 over = self.sql(expression, "over") or "OVER" 2855 2856 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2857 2858 first = expression.args.get("first") 2859 if first is None: 2860 first = "" 2861 else: 2862 first = "FIRST" if first else "LAST" 2863 2864 if not partition and not order and not spec and alias: 2865 return f"{this} {alias}" 2866 2867 args = self.format_args( 2868 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2869 ) 2870 return f"{this} ({args})" 2871 2872 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2873 partition = self.expressions(expression, key="partition_by", flat=True) 2874 return f"PARTITION BY {partition}" if partition else "" 2875 2876 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2877 kind = self.sql(expression, "kind") 2878 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2879 end = ( 2880 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2881 or "CURRENT ROW" 2882 ) 2883 2884 window_spec = f"{kind} BETWEEN {start} AND {end}" 2885 2886 exclude = self.sql(expression, "exclude") 2887 if exclude: 2888 if self.SUPPORTS_WINDOW_EXCLUDE: 2889 window_spec += f" EXCLUDE {exclude}" 2890 else: 2891 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2892 2893 return window_spec 2894 2895 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2896 this = self.sql(expression, "this") 2897 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2898 return f"{this} WITHIN GROUP ({expression_sql})" 2899 2900 def between_sql(self, expression: exp.Between) -> str: 2901 this = self.sql(expression, "this") 2902 low = self.sql(expression, "low") 2903 high = self.sql(expression, "high") 2904 symmetric = expression.args.get("symmetric") 2905 2906 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2907 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2908 2909 flag = ( 2910 " SYMMETRIC" 2911 if symmetric 2912 else " ASYMMETRIC" 2913 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2914 else "" # silently drop ASYMMETRIC – semantics identical 2915 ) 2916 return f"{this} BETWEEN{flag} {low} AND {high}" 2917 2918 def bracket_offset_expressions( 2919 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2920 ) -> t.List[exp.Expression]: 2921 return apply_index_offset( 2922 expression.this, 2923 expression.expressions, 2924 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2925 dialect=self.dialect, 2926 ) 2927 2928 def bracket_sql(self, expression: exp.Bracket) -> str: 2929 expressions = self.bracket_offset_expressions(expression) 2930 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2931 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2932 2933 def all_sql(self, expression: exp.All) -> str: 2934 this = self.sql(expression, "this") 2935 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2936 this = self.wrap(this) 2937 return f"ALL {this}" 2938 2939 def any_sql(self, expression: exp.Any) -> str: 2940 this = self.sql(expression, "this") 2941 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2942 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2943 this = self.wrap(this) 2944 return f"ANY{this}" 2945 return f"ANY {this}" 2946 2947 def exists_sql(self, expression: exp.Exists) -> str: 2948 return f"EXISTS{self.wrap(expression)}" 2949 2950 def case_sql(self, expression: exp.Case) -> str: 2951 this = self.sql(expression, "this") 2952 statements = [f"CASE {this}" if this else "CASE"] 2953 2954 for e in expression.args["ifs"]: 2955 statements.append(f"WHEN {self.sql(e, 'this')}") 2956 statements.append(f"THEN {self.sql(e, 'true')}") 2957 2958 default = self.sql(expression, "default") 2959 2960 if default: 2961 statements.append(f"ELSE {default}") 2962 2963 statements.append("END") 2964 2965 if self.pretty and self.too_wide(statements): 2966 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2967 2968 return " ".join(statements) 2969 2970 def constraint_sql(self, expression: exp.Constraint) -> str: 2971 this = self.sql(expression, "this") 2972 expressions = self.expressions(expression, flat=True) 2973 return f"CONSTRAINT {this} {expressions}" 2974 2975 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2976 order = expression.args.get("order") 2977 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2978 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2979 2980 def extract_sql(self, expression: exp.Extract) -> str: 2981 from sqlglot.dialects.dialect import map_date_part 2982 2983 this = ( 2984 map_date_part(expression.this, self.dialect) 2985 if self.NORMALIZE_EXTRACT_DATE_PARTS 2986 else expression.this 2987 ) 2988 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2989 expression_sql = self.sql(expression, "expression") 2990 2991 return f"EXTRACT({this_sql} FROM {expression_sql})" 2992 2993 def trim_sql(self, expression: exp.Trim) -> str: 2994 trim_type = self.sql(expression, "position") 2995 2996 if trim_type == "LEADING": 2997 func_name = "LTRIM" 2998 elif trim_type == "TRAILING": 2999 func_name = "RTRIM" 3000 else: 3001 func_name = "TRIM" 3002 3003 return self.func(func_name, expression.this, expression.expression) 3004 3005 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3006 args = expression.expressions 3007 if isinstance(expression, exp.ConcatWs): 3008 args = args[1:] # Skip the delimiter 3009 3010 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3011 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3012 3013 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3014 3015 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3016 if not e.type: 3017 from sqlglot.optimizer.annotate_types import annotate_types 3018 3019 e = annotate_types(e, dialect=self.dialect) 3020 3021 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3022 return e 3023 3024 return exp.func("coalesce", e, exp.Literal.string("")) 3025 3026 args = [_wrap_with_coalesce(e) for e in args] 3027 3028 return args 3029 3030 def concat_sql(self, expression: exp.Concat) -> str: 3031 expressions = self.convert_concat_args(expression) 3032 3033 # Some dialects don't allow a single-argument CONCAT call 3034 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3035 return self.sql(expressions[0]) 3036 3037 return self.func("CONCAT", *expressions) 3038 3039 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3040 return self.func( 3041 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3042 ) 3043 3044 def check_sql(self, expression: exp.Check) -> str: 3045 this = self.sql(expression, key="this") 3046 return f"CHECK ({this})" 3047 3048 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3049 expressions = self.expressions(expression, flat=True) 3050 expressions = f" ({expressions})" if expressions else "" 3051 reference = self.sql(expression, "reference") 3052 reference = f" {reference}" if reference else "" 3053 delete = self.sql(expression, "delete") 3054 delete = f" ON DELETE {delete}" if delete else "" 3055 update = self.sql(expression, "update") 3056 update = f" ON UPDATE {update}" if update else "" 3057 options = self.expressions(expression, key="options", flat=True, sep=" ") 3058 options = f" {options}" if options else "" 3059 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3060 3061 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3062 expressions = self.expressions(expression, flat=True) 3063 include = self.sql(expression, "include") 3064 options = self.expressions(expression, key="options", flat=True, sep=" ") 3065 options = f" {options}" if options else "" 3066 return f"PRIMARY KEY ({expressions}){include}{options}" 3067 3068 def if_sql(self, expression: exp.If) -> str: 3069 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3070 3071 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3072 modifier = expression.args.get("modifier") 3073 modifier = f" {modifier}" if modifier else "" 3074 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3075 3076 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3077 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3078 3079 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3080 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3081 3082 if expression.args.get("escape"): 3083 path = self.escape_str(path) 3084 3085 if self.QUOTE_JSON_PATH: 3086 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3087 3088 return path 3089 3090 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3091 if isinstance(expression, exp.JSONPathPart): 3092 transform = self.TRANSFORMS.get(expression.__class__) 3093 if not callable(transform): 3094 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3095 return "" 3096 3097 return transform(self, expression) 3098 3099 if isinstance(expression, int): 3100 return str(expression) 3101 3102 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3103 escaped = expression.replace("'", "\\'") 3104 escaped = f"\\'{expression}\\'" 3105 else: 3106 escaped = expression.replace('"', '\\"') 3107 escaped = f'"{escaped}"' 3108 3109 return escaped 3110 3111 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3112 return f"{self.sql(expression, 'this')} FORMAT JSON" 3113 3114 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3115 # Output the Teradata column FORMAT override. 3116 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3117 this = self.sql(expression, "this") 3118 fmt = self.sql(expression, "format") 3119 return f"{this} (FORMAT {fmt})" 3120 3121 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3122 null_handling = expression.args.get("null_handling") 3123 null_handling = f" {null_handling}" if null_handling else "" 3124 3125 unique_keys = expression.args.get("unique_keys") 3126 if unique_keys is not None: 3127 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3128 else: 3129 unique_keys = "" 3130 3131 return_type = self.sql(expression, "return_type") 3132 return_type = f" RETURNING {return_type}" if return_type else "" 3133 encoding = self.sql(expression, "encoding") 3134 encoding = f" ENCODING {encoding}" if encoding else "" 3135 3136 return self.func( 3137 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3138 *expression.expressions, 3139 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3140 ) 3141 3142 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3143 return self.jsonobject_sql(expression) 3144 3145 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3146 null_handling = expression.args.get("null_handling") 3147 null_handling = f" {null_handling}" if null_handling else "" 3148 return_type = self.sql(expression, "return_type") 3149 return_type = f" RETURNING {return_type}" if return_type else "" 3150 strict = " STRICT" if expression.args.get("strict") else "" 3151 return self.func( 3152 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3153 ) 3154 3155 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3156 this = self.sql(expression, "this") 3157 order = self.sql(expression, "order") 3158 null_handling = expression.args.get("null_handling") 3159 null_handling = f" {null_handling}" if null_handling else "" 3160 return_type = self.sql(expression, "return_type") 3161 return_type = f" RETURNING {return_type}" if return_type else "" 3162 strict = " STRICT" if expression.args.get("strict") else "" 3163 return self.func( 3164 "JSON_ARRAYAGG", 3165 this, 3166 suffix=f"{order}{null_handling}{return_type}{strict})", 3167 ) 3168 3169 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3170 path = self.sql(expression, "path") 3171 path = f" PATH {path}" if path else "" 3172 nested_schema = self.sql(expression, "nested_schema") 3173 3174 if nested_schema: 3175 return f"NESTED{path} {nested_schema}" 3176 3177 this = self.sql(expression, "this") 3178 kind = self.sql(expression, "kind") 3179 kind = f" {kind}" if kind else "" 3180 return f"{this}{kind}{path}" 3181 3182 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3183 return self.func("COLUMNS", *expression.expressions) 3184 3185 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3186 this = self.sql(expression, "this") 3187 path = self.sql(expression, "path") 3188 path = f", {path}" if path else "" 3189 error_handling = expression.args.get("error_handling") 3190 error_handling = f" {error_handling}" if error_handling else "" 3191 empty_handling = expression.args.get("empty_handling") 3192 empty_handling = f" {empty_handling}" if empty_handling else "" 3193 schema = self.sql(expression, "schema") 3194 return self.func( 3195 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3196 ) 3197 3198 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3199 this = self.sql(expression, "this") 3200 kind = self.sql(expression, "kind") 3201 path = self.sql(expression, "path") 3202 path = f" {path}" if path else "" 3203 as_json = " AS JSON" if expression.args.get("as_json") else "" 3204 return f"{this} {kind}{path}{as_json}" 3205 3206 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3207 this = self.sql(expression, "this") 3208 path = self.sql(expression, "path") 3209 path = f", {path}" if path else "" 3210 expressions = self.expressions(expression) 3211 with_ = ( 3212 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3213 if expressions 3214 else "" 3215 ) 3216 return f"OPENJSON({this}{path}){with_}" 3217 3218 def in_sql(self, expression: exp.In) -> str: 3219 query = expression.args.get("query") 3220 unnest = expression.args.get("unnest") 3221 field = expression.args.get("field") 3222 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3223 3224 if query: 3225 in_sql = self.sql(query) 3226 elif unnest: 3227 in_sql = self.in_unnest_op(unnest) 3228 elif field: 3229 in_sql = self.sql(field) 3230 else: 3231 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3232 3233 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3234 3235 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3236 return f"(SELECT {self.sql(unnest)})" 3237 3238 def interval_sql(self, expression: exp.Interval) -> str: 3239 unit = self.sql(expression, "unit") 3240 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3241 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3242 unit = f" {unit}" if unit else "" 3243 3244 if self.SINGLE_STRING_INTERVAL: 3245 this = expression.this.name if expression.this else "" 3246 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3247 3248 this = self.sql(expression, "this") 3249 if this: 3250 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3251 this = f" {this}" if unwrapped else f" ({this})" 3252 3253 return f"INTERVAL{this}{unit}" 3254 3255 def return_sql(self, expression: exp.Return) -> str: 3256 return f"RETURN {self.sql(expression, 'this')}" 3257 3258 def reference_sql(self, expression: exp.Reference) -> str: 3259 this = self.sql(expression, "this") 3260 expressions = self.expressions(expression, flat=True) 3261 expressions = f"({expressions})" if expressions else "" 3262 options = self.expressions(expression, key="options", flat=True, sep=" ") 3263 options = f" {options}" if options else "" 3264 return f"REFERENCES {this}{expressions}{options}" 3265 3266 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3267 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3268 parent = expression.parent 3269 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3270 return self.func( 3271 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3272 ) 3273 3274 def paren_sql(self, expression: exp.Paren) -> str: 3275 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3276 return f"({sql}{self.seg(')', sep='')}" 3277 3278 def neg_sql(self, expression: exp.Neg) -> str: 3279 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3280 this_sql = self.sql(expression, "this") 3281 sep = " " if this_sql[0] == "-" else "" 3282 return f"-{sep}{this_sql}" 3283 3284 def not_sql(self, expression: exp.Not) -> str: 3285 return f"NOT {self.sql(expression, 'this')}" 3286 3287 def alias_sql(self, expression: exp.Alias) -> str: 3288 alias = self.sql(expression, "alias") 3289 alias = f" AS {alias}" if alias else "" 3290 return f"{self.sql(expression, 'this')}{alias}" 3291 3292 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3293 alias = expression.args["alias"] 3294 3295 parent = expression.parent 3296 pivot = parent and parent.parent 3297 3298 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3299 identifier_alias = isinstance(alias, exp.Identifier) 3300 literal_alias = isinstance(alias, exp.Literal) 3301 3302 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3303 alias.replace(exp.Literal.string(alias.output_name)) 3304 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3305 alias.replace(exp.to_identifier(alias.output_name)) 3306 3307 return self.alias_sql(expression) 3308 3309 def aliases_sql(self, expression: exp.Aliases) -> str: 3310 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3311 3312 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3313 this = self.sql(expression, "this") 3314 index = self.sql(expression, "expression") 3315 return f"{this} AT {index}" 3316 3317 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3318 this = self.sql(expression, "this") 3319 zone = self.sql(expression, "zone") 3320 return f"{this} AT TIME ZONE {zone}" 3321 3322 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3323 this = self.sql(expression, "this") 3324 zone = self.sql(expression, "zone") 3325 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3326 3327 def add_sql(self, expression: exp.Add) -> str: 3328 return self.binary(expression, "+") 3329 3330 def and_sql( 3331 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3332 ) -> str: 3333 return self.connector_sql(expression, "AND", stack) 3334 3335 def or_sql( 3336 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3337 ) -> str: 3338 return self.connector_sql(expression, "OR", stack) 3339 3340 def xor_sql( 3341 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3342 ) -> str: 3343 return self.connector_sql(expression, "XOR", stack) 3344 3345 def connector_sql( 3346 self, 3347 expression: exp.Connector, 3348 op: str, 3349 stack: t.Optional[t.List[str | exp.Expression]] = None, 3350 ) -> str: 3351 if stack is not None: 3352 if expression.expressions: 3353 stack.append(self.expressions(expression, sep=f" {op} ")) 3354 else: 3355 stack.append(expression.right) 3356 if expression.comments and self.comments: 3357 for comment in expression.comments: 3358 if comment: 3359 op += f" /*{self.sanitize_comment(comment)}*/" 3360 stack.extend((op, expression.left)) 3361 return op 3362 3363 stack = [expression] 3364 sqls: t.List[str] = [] 3365 ops = set() 3366 3367 while stack: 3368 node = stack.pop() 3369 if isinstance(node, exp.Connector): 3370 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3371 else: 3372 sql = self.sql(node) 3373 if sqls and sqls[-1] in ops: 3374 sqls[-1] += f" {sql}" 3375 else: 3376 sqls.append(sql) 3377 3378 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3379 return sep.join(sqls) 3380 3381 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3382 return self.binary(expression, "&") 3383 3384 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3385 return self.binary(expression, "<<") 3386 3387 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3388 return f"~{self.sql(expression, 'this')}" 3389 3390 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3391 return self.binary(expression, "|") 3392 3393 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3394 return self.binary(expression, ">>") 3395 3396 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3397 return self.binary(expression, "^") 3398 3399 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3400 format_sql = self.sql(expression, "format") 3401 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3402 to_sql = self.sql(expression, "to") 3403 to_sql = f" {to_sql}" if to_sql else "" 3404 action = self.sql(expression, "action") 3405 action = f" {action}" if action else "" 3406 default = self.sql(expression, "default") 3407 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3408 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3409 3410 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3411 zone = self.sql(expression, "this") 3412 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3413 3414 def collate_sql(self, expression: exp.Collate) -> str: 3415 if self.COLLATE_IS_FUNC: 3416 return self.function_fallback_sql(expression) 3417 return self.binary(expression, "COLLATE") 3418 3419 def command_sql(self, expression: exp.Command) -> str: 3420 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3421 3422 def comment_sql(self, expression: exp.Comment) -> str: 3423 this = self.sql(expression, "this") 3424 kind = expression.args["kind"] 3425 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3426 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3427 expression_sql = self.sql(expression, "expression") 3428 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3429 3430 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3431 this = self.sql(expression, "this") 3432 delete = " DELETE" if expression.args.get("delete") else "" 3433 recompress = self.sql(expression, "recompress") 3434 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3435 to_disk = self.sql(expression, "to_disk") 3436 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3437 to_volume = self.sql(expression, "to_volume") 3438 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3439 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3440 3441 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3442 where = self.sql(expression, "where") 3443 group = self.sql(expression, "group") 3444 aggregates = self.expressions(expression, key="aggregates") 3445 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3446 3447 if not (where or group or aggregates) and len(expression.expressions) == 1: 3448 return f"TTL {self.expressions(expression, flat=True)}" 3449 3450 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3451 3452 def transaction_sql(self, expression: exp.Transaction) -> str: 3453 return "BEGIN" 3454 3455 def commit_sql(self, expression: exp.Commit) -> str: 3456 chain = expression.args.get("chain") 3457 if chain is not None: 3458 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3459 3460 return f"COMMIT{chain or ''}" 3461 3462 def rollback_sql(self, expression: exp.Rollback) -> str: 3463 savepoint = expression.args.get("savepoint") 3464 savepoint = f" TO {savepoint}" if savepoint else "" 3465 return f"ROLLBACK{savepoint}" 3466 3467 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3468 this = self.sql(expression, "this") 3469 3470 dtype = self.sql(expression, "dtype") 3471 if dtype: 3472 collate = self.sql(expression, "collate") 3473 collate = f" COLLATE {collate}" if collate else "" 3474 using = self.sql(expression, "using") 3475 using = f" USING {using}" if using else "" 3476 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3477 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3478 3479 default = self.sql(expression, "default") 3480 if default: 3481 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3482 3483 comment = self.sql(expression, "comment") 3484 if comment: 3485 return f"ALTER COLUMN {this} COMMENT {comment}" 3486 3487 visible = expression.args.get("visible") 3488 if visible: 3489 return f"ALTER COLUMN {this} SET {visible}" 3490 3491 allow_null = expression.args.get("allow_null") 3492 drop = expression.args.get("drop") 3493 3494 if not drop and not allow_null: 3495 self.unsupported("Unsupported ALTER COLUMN syntax") 3496 3497 if allow_null is not None: 3498 keyword = "DROP" if drop else "SET" 3499 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3500 3501 return f"ALTER COLUMN {this} DROP DEFAULT" 3502 3503 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3504 this = self.sql(expression, "this") 3505 3506 visible = expression.args.get("visible") 3507 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3508 3509 return f"ALTER INDEX {this} {visible_sql}" 3510 3511 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3512 this = self.sql(expression, "this") 3513 if not isinstance(expression.this, exp.Var): 3514 this = f"KEY DISTKEY {this}" 3515 return f"ALTER DISTSTYLE {this}" 3516 3517 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3518 compound = " COMPOUND" if expression.args.get("compound") else "" 3519 this = self.sql(expression, "this") 3520 expressions = self.expressions(expression, flat=True) 3521 expressions = f"({expressions})" if expressions else "" 3522 return f"ALTER{compound} SORTKEY {this or expressions}" 3523 3524 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3525 if not self.RENAME_TABLE_WITH_DB: 3526 # Remove db from tables 3527 expression = expression.transform( 3528 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3529 ).assert_is(exp.AlterRename) 3530 this = self.sql(expression, "this") 3531 to_kw = " TO" if include_to else "" 3532 return f"RENAME{to_kw} {this}" 3533 3534 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3535 exists = " IF EXISTS" if expression.args.get("exists") else "" 3536 old_column = self.sql(expression, "this") 3537 new_column = self.sql(expression, "to") 3538 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3539 3540 def alterset_sql(self, expression: exp.AlterSet) -> str: 3541 exprs = self.expressions(expression, flat=True) 3542 if self.ALTER_SET_WRAPPED: 3543 exprs = f"({exprs})" 3544 3545 return f"SET {exprs}" 3546 3547 def alter_sql(self, expression: exp.Alter) -> str: 3548 actions = expression.args["actions"] 3549 3550 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3551 actions[0], exp.ColumnDef 3552 ): 3553 actions_sql = self.expressions(expression, key="actions", flat=True) 3554 actions_sql = f"ADD {actions_sql}" 3555 else: 3556 actions_list = [] 3557 for action in actions: 3558 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3559 action_sql = self.add_column_sql(action) 3560 else: 3561 action_sql = self.sql(action) 3562 if isinstance(action, exp.Query): 3563 action_sql = f"AS {action_sql}" 3564 3565 actions_list.append(action_sql) 3566 3567 actions_sql = self.format_args(*actions_list).lstrip("\n") 3568 3569 exists = " IF EXISTS" if expression.args.get("exists") else "" 3570 on_cluster = self.sql(expression, "cluster") 3571 on_cluster = f" {on_cluster}" if on_cluster else "" 3572 only = " ONLY" if expression.args.get("only") else "" 3573 options = self.expressions(expression, key="options") 3574 options = f", {options}" if options else "" 3575 kind = self.sql(expression, "kind") 3576 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3577 check = " WITH CHECK" if expression.args.get("check") else "" 3578 this = self.sql(expression, "this") 3579 this = f" {this}" if this else "" 3580 3581 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3582 3583 def altersession_sql(self, expression: exp.AlterSession) -> str: 3584 items_sql = self.expressions(expression, flat=True) 3585 keyword = "UNSET" if expression.args.get("unset") else "SET" 3586 return f"{keyword} {items_sql}" 3587 3588 def add_column_sql(self, expression: exp.Expression) -> str: 3589 sql = self.sql(expression) 3590 if isinstance(expression, exp.Schema): 3591 column_text = " COLUMNS" 3592 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3593 column_text = " COLUMN" 3594 else: 3595 column_text = "" 3596 3597 return f"ADD{column_text} {sql}" 3598 3599 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3600 expressions = self.expressions(expression) 3601 exists = " IF EXISTS " if expression.args.get("exists") else " " 3602 return f"DROP{exists}{expressions}" 3603 3604 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3605 return f"ADD {self.expressions(expression, indent=False)}" 3606 3607 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3608 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3609 location = self.sql(expression, "location") 3610 location = f" {location}" if location else "" 3611 return f"ADD {exists}{self.sql(expression.this)}{location}" 3612 3613 def distinct_sql(self, expression: exp.Distinct) -> str: 3614 this = self.expressions(expression, flat=True) 3615 3616 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3617 case = exp.case() 3618 for arg in expression.expressions: 3619 case = case.when(arg.is_(exp.null()), exp.null()) 3620 this = self.sql(case.else_(f"({this})")) 3621 3622 this = f" {this}" if this else "" 3623 3624 on = self.sql(expression, "on") 3625 on = f" ON {on}" if on else "" 3626 return f"DISTINCT{this}{on}" 3627 3628 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3629 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3630 3631 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3632 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3633 3634 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3635 this_sql = self.sql(expression, "this") 3636 expression_sql = self.sql(expression, "expression") 3637 kind = "MAX" if expression.args.get("max") else "MIN" 3638 return f"{this_sql} HAVING {kind} {expression_sql}" 3639 3640 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3641 return self.sql( 3642 exp.Cast( 3643 this=exp.Div(this=expression.this, expression=expression.expression), 3644 to=exp.DataType(this=exp.DataType.Type.INT), 3645 ) 3646 ) 3647 3648 def dpipe_sql(self, expression: exp.DPipe) -> str: 3649 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3650 return self.func( 3651 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3652 ) 3653 return self.binary(expression, "||") 3654 3655 def div_sql(self, expression: exp.Div) -> str: 3656 l, r = expression.left, expression.right 3657 3658 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3659 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3660 3661 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3662 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3663 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3664 3665 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3666 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3667 return self.sql( 3668 exp.cast( 3669 l / r, 3670 to=exp.DataType.Type.BIGINT, 3671 ) 3672 ) 3673 3674 return self.binary(expression, "/") 3675 3676 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3677 n = exp._wrap(expression.this, exp.Binary) 3678 d = exp._wrap(expression.expression, exp.Binary) 3679 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3680 3681 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3682 return self.binary(expression, "OVERLAPS") 3683 3684 def distance_sql(self, expression: exp.Distance) -> str: 3685 return self.binary(expression, "<->") 3686 3687 def dot_sql(self, expression: exp.Dot) -> str: 3688 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3689 3690 def eq_sql(self, expression: exp.EQ) -> str: 3691 return self.binary(expression, "=") 3692 3693 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3694 return self.binary(expression, ":=") 3695 3696 def escape_sql(self, expression: exp.Escape) -> str: 3697 return self.binary(expression, "ESCAPE") 3698 3699 def glob_sql(self, expression: exp.Glob) -> str: 3700 return self.binary(expression, "GLOB") 3701 3702 def gt_sql(self, expression: exp.GT) -> str: 3703 return self.binary(expression, ">") 3704 3705 def gte_sql(self, expression: exp.GTE) -> str: 3706 return self.binary(expression, ">=") 3707 3708 def is_sql(self, expression: exp.Is) -> str: 3709 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3710 return self.sql( 3711 expression.this if expression.expression.this else exp.not_(expression.this) 3712 ) 3713 return self.binary(expression, "IS") 3714 3715 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3716 this = expression.this 3717 rhs = expression.expression 3718 3719 if isinstance(expression, exp.Like): 3720 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3721 op = "LIKE" 3722 else: 3723 exp_class = exp.ILike 3724 op = "ILIKE" 3725 3726 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3727 exprs = rhs.this.unnest() 3728 3729 if isinstance(exprs, exp.Tuple): 3730 exprs = exprs.expressions 3731 3732 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3733 3734 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3735 for expr in exprs[1:]: 3736 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3737 3738 return self.sql(like_expr) 3739 3740 return self.binary(expression, op) 3741 3742 def like_sql(self, expression: exp.Like) -> str: 3743 return self._like_sql(expression) 3744 3745 def ilike_sql(self, expression: exp.ILike) -> str: 3746 return self._like_sql(expression) 3747 3748 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3749 return self.binary(expression, "SIMILAR TO") 3750 3751 def lt_sql(self, expression: exp.LT) -> str: 3752 return self.binary(expression, "<") 3753 3754 def lte_sql(self, expression: exp.LTE) -> str: 3755 return self.binary(expression, "<=") 3756 3757 def mod_sql(self, expression: exp.Mod) -> str: 3758 return self.binary(expression, "%") 3759 3760 def mul_sql(self, expression: exp.Mul) -> str: 3761 return self.binary(expression, "*") 3762 3763 def neq_sql(self, expression: exp.NEQ) -> str: 3764 return self.binary(expression, "<>") 3765 3766 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3767 return self.binary(expression, "IS NOT DISTINCT FROM") 3768 3769 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3770 return self.binary(expression, "IS DISTINCT FROM") 3771 3772 def slice_sql(self, expression: exp.Slice) -> str: 3773 return self.binary(expression, ":") 3774 3775 def sub_sql(self, expression: exp.Sub) -> str: 3776 return self.binary(expression, "-") 3777 3778 def trycast_sql(self, expression: exp.TryCast) -> str: 3779 return self.cast_sql(expression, safe_prefix="TRY_") 3780 3781 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3782 return self.cast_sql(expression) 3783 3784 def try_sql(self, expression: exp.Try) -> str: 3785 if not self.TRY_SUPPORTED: 3786 self.unsupported("Unsupported TRY function") 3787 return self.sql(expression, "this") 3788 3789 return self.func("TRY", expression.this) 3790 3791 def log_sql(self, expression: exp.Log) -> str: 3792 this = expression.this 3793 expr = expression.expression 3794 3795 if self.dialect.LOG_BASE_FIRST is False: 3796 this, expr = expr, this 3797 elif self.dialect.LOG_BASE_FIRST is None and expr: 3798 if this.name in ("2", "10"): 3799 return self.func(f"LOG{this.name}", expr) 3800 3801 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3802 3803 return self.func("LOG", this, expr) 3804 3805 def use_sql(self, expression: exp.Use) -> str: 3806 kind = self.sql(expression, "kind") 3807 kind = f" {kind}" if kind else "" 3808 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3809 this = f" {this}" if this else "" 3810 return f"USE{kind}{this}" 3811 3812 def binary(self, expression: exp.Binary, op: str) -> str: 3813 sqls: t.List[str] = [] 3814 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3815 binary_type = type(expression) 3816 3817 while stack: 3818 node = stack.pop() 3819 3820 if type(node) is binary_type: 3821 op_func = node.args.get("operator") 3822 if op_func: 3823 op = f"OPERATOR({self.sql(op_func)})" 3824 3825 stack.append(node.right) 3826 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3827 stack.append(node.left) 3828 else: 3829 sqls.append(self.sql(node)) 3830 3831 return "".join(sqls) 3832 3833 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3834 to_clause = self.sql(expression, "to") 3835 if to_clause: 3836 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3837 3838 return self.function_fallback_sql(expression) 3839 3840 def function_fallback_sql(self, expression: exp.Func) -> str: 3841 args = [] 3842 3843 for key in expression.arg_types: 3844 arg_value = expression.args.get(key) 3845 3846 if isinstance(arg_value, list): 3847 for value in arg_value: 3848 args.append(value) 3849 elif arg_value is not None: 3850 args.append(arg_value) 3851 3852 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3853 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3854 else: 3855 name = expression.sql_name() 3856 3857 return self.func(name, *args) 3858 3859 def func( 3860 self, 3861 name: str, 3862 *args: t.Optional[exp.Expression | str], 3863 prefix: str = "(", 3864 suffix: str = ")", 3865 normalize: bool = True, 3866 ) -> str: 3867 name = self.normalize_func(name) if normalize else name 3868 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3869 3870 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3871 arg_sqls = tuple( 3872 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3873 ) 3874 if self.pretty and self.too_wide(arg_sqls): 3875 return self.indent( 3876 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3877 ) 3878 return sep.join(arg_sqls) 3879 3880 def too_wide(self, args: t.Iterable) -> bool: 3881 return sum(len(arg) for arg in args) > self.max_text_width 3882 3883 def format_time( 3884 self, 3885 expression: exp.Expression, 3886 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3887 inverse_time_trie: t.Optional[t.Dict] = None, 3888 ) -> t.Optional[str]: 3889 return format_time( 3890 self.sql(expression, "format"), 3891 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3892 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3893 ) 3894 3895 def expressions( 3896 self, 3897 expression: t.Optional[exp.Expression] = None, 3898 key: t.Optional[str] = None, 3899 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3900 flat: bool = False, 3901 indent: bool = True, 3902 skip_first: bool = False, 3903 skip_last: bool = False, 3904 sep: str = ", ", 3905 prefix: str = "", 3906 dynamic: bool = False, 3907 new_line: bool = False, 3908 ) -> str: 3909 expressions = expression.args.get(key or "expressions") if expression else sqls 3910 3911 if not expressions: 3912 return "" 3913 3914 if flat: 3915 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3916 3917 num_sqls = len(expressions) 3918 result_sqls = [] 3919 3920 for i, e in enumerate(expressions): 3921 sql = self.sql(e, comment=False) 3922 if not sql: 3923 continue 3924 3925 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3926 3927 if self.pretty: 3928 if self.leading_comma: 3929 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3930 else: 3931 result_sqls.append( 3932 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3933 ) 3934 else: 3935 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3936 3937 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3938 if new_line: 3939 result_sqls.insert(0, "") 3940 result_sqls.append("") 3941 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3942 else: 3943 result_sql = "".join(result_sqls) 3944 3945 return ( 3946 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3947 if indent 3948 else result_sql 3949 ) 3950 3951 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3952 flat = flat or isinstance(expression.parent, exp.Properties) 3953 expressions_sql = self.expressions(expression, flat=flat) 3954 if flat: 3955 return f"{op} {expressions_sql}" 3956 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3957 3958 def naked_property(self, expression: exp.Property) -> str: 3959 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3960 if not property_name: 3961 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3962 return f"{property_name} {self.sql(expression, 'this')}" 3963 3964 def tag_sql(self, expression: exp.Tag) -> str: 3965 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3966 3967 def token_sql(self, token_type: TokenType) -> str: 3968 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3969 3970 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3971 this = self.sql(expression, "this") 3972 expressions = self.no_identify(self.expressions, expression) 3973 expressions = ( 3974 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3975 ) 3976 return f"{this}{expressions}" if expressions.strip() != "" else this 3977 3978 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3979 this = self.sql(expression, "this") 3980 expressions = self.expressions(expression, flat=True) 3981 return f"{this}({expressions})" 3982 3983 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3984 return self.binary(expression, "=>") 3985 3986 def when_sql(self, expression: exp.When) -> str: 3987 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3988 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3989 condition = self.sql(expression, "condition") 3990 condition = f" AND {condition}" if condition else "" 3991 3992 then_expression = expression.args.get("then") 3993 if isinstance(then_expression, exp.Insert): 3994 this = self.sql(then_expression, "this") 3995 this = f"INSERT {this}" if this else "INSERT" 3996 then = self.sql(then_expression, "expression") 3997 then = f"{this} VALUES {then}" if then else this 3998 elif isinstance(then_expression, exp.Update): 3999 if isinstance(then_expression.args.get("expressions"), exp.Star): 4000 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4001 else: 4002 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4003 else: 4004 then = self.sql(then_expression) 4005 return f"WHEN {matched}{source}{condition} THEN {then}" 4006 4007 def whens_sql(self, expression: exp.Whens) -> str: 4008 return self.expressions(expression, sep=" ", indent=False) 4009 4010 def merge_sql(self, expression: exp.Merge) -> str: 4011 table = expression.this 4012 table_alias = "" 4013 4014 hints = table.args.get("hints") 4015 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4016 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4017 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4018 4019 this = self.sql(table) 4020 using = f"USING {self.sql(expression, 'using')}" 4021 on = f"ON {self.sql(expression, 'on')}" 4022 whens = self.sql(expression, "whens") 4023 4024 returning = self.sql(expression, "returning") 4025 if returning: 4026 whens = f"{whens}{returning}" 4027 4028 sep = self.sep() 4029 4030 return self.prepend_ctes( 4031 expression, 4032 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4033 ) 4034 4035 @unsupported_args("format") 4036 def tochar_sql(self, expression: exp.ToChar) -> str: 4037 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4038 4039 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4040 if not self.SUPPORTS_TO_NUMBER: 4041 self.unsupported("Unsupported TO_NUMBER function") 4042 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4043 4044 fmt = expression.args.get("format") 4045 if not fmt: 4046 self.unsupported("Conversion format is required for TO_NUMBER") 4047 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4048 4049 return self.func("TO_NUMBER", expression.this, fmt) 4050 4051 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4052 this = self.sql(expression, "this") 4053 kind = self.sql(expression, "kind") 4054 settings_sql = self.expressions(expression, key="settings", sep=" ") 4055 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4056 return f"{this}({kind}{args})" 4057 4058 def dictrange_sql(self, expression: exp.DictRange) -> str: 4059 this = self.sql(expression, "this") 4060 max = self.sql(expression, "max") 4061 min = self.sql(expression, "min") 4062 return f"{this}(MIN {min} MAX {max})" 4063 4064 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4065 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4066 4067 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4068 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4069 4070 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4071 def uniquekeyproperty_sql( 4072 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4073 ) -> str: 4074 return f"{prefix} ({self.expressions(expression, flat=True)})" 4075 4076 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4077 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4078 expressions = self.expressions(expression, flat=True) 4079 expressions = f" {self.wrap(expressions)}" if expressions else "" 4080 buckets = self.sql(expression, "buckets") 4081 kind = self.sql(expression, "kind") 4082 buckets = f" BUCKETS {buckets}" if buckets else "" 4083 order = self.sql(expression, "order") 4084 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4085 4086 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4087 return "" 4088 4089 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4090 expressions = self.expressions(expression, key="expressions", flat=True) 4091 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4092 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4093 buckets = self.sql(expression, "buckets") 4094 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4095 4096 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4097 this = self.sql(expression, "this") 4098 having = self.sql(expression, "having") 4099 4100 if having: 4101 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4102 4103 return self.func("ANY_VALUE", this) 4104 4105 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4106 transform = self.func("TRANSFORM", *expression.expressions) 4107 row_format_before = self.sql(expression, "row_format_before") 4108 row_format_before = f" {row_format_before}" if row_format_before else "" 4109 record_writer = self.sql(expression, "record_writer") 4110 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4111 using = f" USING {self.sql(expression, 'command_script')}" 4112 schema = self.sql(expression, "schema") 4113 schema = f" AS {schema}" if schema else "" 4114 row_format_after = self.sql(expression, "row_format_after") 4115 row_format_after = f" {row_format_after}" if row_format_after else "" 4116 record_reader = self.sql(expression, "record_reader") 4117 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4118 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4119 4120 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4121 key_block_size = self.sql(expression, "key_block_size") 4122 if key_block_size: 4123 return f"KEY_BLOCK_SIZE = {key_block_size}" 4124 4125 using = self.sql(expression, "using") 4126 if using: 4127 return f"USING {using}" 4128 4129 parser = self.sql(expression, "parser") 4130 if parser: 4131 return f"WITH PARSER {parser}" 4132 4133 comment = self.sql(expression, "comment") 4134 if comment: 4135 return f"COMMENT {comment}" 4136 4137 visible = expression.args.get("visible") 4138 if visible is not None: 4139 return "VISIBLE" if visible else "INVISIBLE" 4140 4141 engine_attr = self.sql(expression, "engine_attr") 4142 if engine_attr: 4143 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4144 4145 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4146 if secondary_engine_attr: 4147 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4148 4149 self.unsupported("Unsupported index constraint option.") 4150 return "" 4151 4152 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4153 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4154 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4155 4156 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4157 kind = self.sql(expression, "kind") 4158 kind = f"{kind} INDEX" if kind else "INDEX" 4159 this = self.sql(expression, "this") 4160 this = f" {this}" if this else "" 4161 index_type = self.sql(expression, "index_type") 4162 index_type = f" USING {index_type}" if index_type else "" 4163 expressions = self.expressions(expression, flat=True) 4164 expressions = f" ({expressions})" if expressions else "" 4165 options = self.expressions(expression, key="options", sep=" ") 4166 options = f" {options}" if options else "" 4167 return f"{kind}{this}{index_type}{expressions}{options}" 4168 4169 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4170 if self.NVL2_SUPPORTED: 4171 return self.function_fallback_sql(expression) 4172 4173 case = exp.Case().when( 4174 expression.this.is_(exp.null()).not_(copy=False), 4175 expression.args["true"], 4176 copy=False, 4177 ) 4178 else_cond = expression.args.get("false") 4179 if else_cond: 4180 case.else_(else_cond, copy=False) 4181 4182 return self.sql(case) 4183 4184 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4185 this = self.sql(expression, "this") 4186 expr = self.sql(expression, "expression") 4187 iterator = self.sql(expression, "iterator") 4188 condition = self.sql(expression, "condition") 4189 condition = f" IF {condition}" if condition else "" 4190 return f"{this} FOR {expr} IN {iterator}{condition}" 4191 4192 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4193 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4194 4195 def opclass_sql(self, expression: exp.Opclass) -> str: 4196 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4197 4198 def predict_sql(self, expression: exp.Predict) -> str: 4199 model = self.sql(expression, "this") 4200 model = f"MODEL {model}" 4201 table = self.sql(expression, "expression") 4202 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4203 parameters = self.sql(expression, "params_struct") 4204 return self.func("PREDICT", model, table, parameters or None) 4205 4206 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4207 model = self.sql(expression, "this") 4208 model = f"MODEL {model}" 4209 table = self.sql(expression, "expression") 4210 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4211 parameters = self.sql(expression, "params_struct") 4212 return self.func("GENERATE_EMBEDDING", model, table, parameters or None) 4213 4214 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4215 this_sql = self.sql(expression, "this") 4216 if isinstance(expression.this, exp.Table): 4217 this_sql = f"TABLE {this_sql}" 4218 4219 return self.func( 4220 "FEATURES_AT_TIME", 4221 this_sql, 4222 expression.args.get("time"), 4223 expression.args.get("num_rows"), 4224 expression.args.get("ignore_feature_nulls"), 4225 ) 4226 4227 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4228 this_sql = self.sql(expression, "this") 4229 if isinstance(expression.this, exp.Table): 4230 this_sql = f"TABLE {this_sql}" 4231 4232 query_table = self.sql(expression, "query_table") 4233 if isinstance(expression.args["query_table"], exp.Table): 4234 query_table = f"TABLE {query_table}" 4235 4236 return self.func( 4237 "VECTOR_SEARCH", 4238 this_sql, 4239 expression.args.get("column_to_search"), 4240 query_table, 4241 expression.args.get("query_column_to_search"), 4242 expression.args.get("top_k"), 4243 expression.args.get("distance_type"), 4244 expression.args.get("options"), 4245 ) 4246 4247 def forin_sql(self, expression: exp.ForIn) -> str: 4248 this = self.sql(expression, "this") 4249 expression_sql = self.sql(expression, "expression") 4250 return f"FOR {this} DO {expression_sql}" 4251 4252 def refresh_sql(self, expression: exp.Refresh) -> str: 4253 this = self.sql(expression, "this") 4254 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4255 return f"REFRESH {table}{this}" 4256 4257 def toarray_sql(self, expression: exp.ToArray) -> str: 4258 arg = expression.this 4259 if not arg.type: 4260 from sqlglot.optimizer.annotate_types import annotate_types 4261 4262 arg = annotate_types(arg, dialect=self.dialect) 4263 4264 if arg.is_type(exp.DataType.Type.ARRAY): 4265 return self.sql(arg) 4266 4267 cond_for_null = arg.is_(exp.null()) 4268 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4269 4270 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4271 this = expression.this 4272 time_format = self.format_time(expression) 4273 4274 if time_format: 4275 return self.sql( 4276 exp.cast( 4277 exp.StrToTime(this=this, format=expression.args["format"]), 4278 exp.DataType.Type.TIME, 4279 ) 4280 ) 4281 4282 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4283 return self.sql(this) 4284 4285 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4286 4287 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4288 this = expression.this 4289 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4290 return self.sql(this) 4291 4292 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4293 4294 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4295 this = expression.this 4296 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4297 return self.sql(this) 4298 4299 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4300 4301 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4302 this = expression.this 4303 time_format = self.format_time(expression) 4304 4305 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4306 return self.sql( 4307 exp.cast( 4308 exp.StrToTime(this=this, format=expression.args["format"]), 4309 exp.DataType.Type.DATE, 4310 ) 4311 ) 4312 4313 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4314 return self.sql(this) 4315 4316 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4317 4318 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4319 return self.sql( 4320 exp.func( 4321 "DATEDIFF", 4322 expression.this, 4323 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4324 "day", 4325 ) 4326 ) 4327 4328 def lastday_sql(self, expression: exp.LastDay) -> str: 4329 if self.LAST_DAY_SUPPORTS_DATE_PART: 4330 return self.function_fallback_sql(expression) 4331 4332 unit = expression.text("unit") 4333 if unit and unit != "MONTH": 4334 self.unsupported("Date parts are not supported in LAST_DAY.") 4335 4336 return self.func("LAST_DAY", expression.this) 4337 4338 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4339 from sqlglot.dialects.dialect import unit_to_str 4340 4341 return self.func( 4342 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4343 ) 4344 4345 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4346 if self.CAN_IMPLEMENT_ARRAY_ANY: 4347 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4348 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4349 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4350 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4351 4352 from sqlglot.dialects import Dialect 4353 4354 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4355 if self.dialect.__class__ != Dialect: 4356 self.unsupported("ARRAY_ANY is unsupported") 4357 4358 return self.function_fallback_sql(expression) 4359 4360 def struct_sql(self, expression: exp.Struct) -> str: 4361 expression.set( 4362 "expressions", 4363 [ 4364 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4365 if isinstance(e, exp.PropertyEQ) 4366 else e 4367 for e in expression.expressions 4368 ], 4369 ) 4370 4371 return self.function_fallback_sql(expression) 4372 4373 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4374 low = self.sql(expression, "this") 4375 high = self.sql(expression, "expression") 4376 4377 return f"{low} TO {high}" 4378 4379 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4380 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4381 tables = f" {self.expressions(expression)}" 4382 4383 exists = " IF EXISTS" if expression.args.get("exists") else "" 4384 4385 on_cluster = self.sql(expression, "cluster") 4386 on_cluster = f" {on_cluster}" if on_cluster else "" 4387 4388 identity = self.sql(expression, "identity") 4389 identity = f" {identity} IDENTITY" if identity else "" 4390 4391 option = self.sql(expression, "option") 4392 option = f" {option}" if option else "" 4393 4394 partition = self.sql(expression, "partition") 4395 partition = f" {partition}" if partition else "" 4396 4397 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4398 4399 # This transpiles T-SQL's CONVERT function 4400 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4401 def convert_sql(self, expression: exp.Convert) -> str: 4402 to = expression.this 4403 value = expression.expression 4404 style = expression.args.get("style") 4405 safe = expression.args.get("safe") 4406 strict = expression.args.get("strict") 4407 4408 if not to or not value: 4409 return "" 4410 4411 # Retrieve length of datatype and override to default if not specified 4412 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4413 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4414 4415 transformed: t.Optional[exp.Expression] = None 4416 cast = exp.Cast if strict else exp.TryCast 4417 4418 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4419 if isinstance(style, exp.Literal) and style.is_int: 4420 from sqlglot.dialects.tsql import TSQL 4421 4422 style_value = style.name 4423 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4424 if not converted_style: 4425 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4426 4427 fmt = exp.Literal.string(converted_style) 4428 4429 if to.this == exp.DataType.Type.DATE: 4430 transformed = exp.StrToDate(this=value, format=fmt) 4431 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4432 transformed = exp.StrToTime(this=value, format=fmt) 4433 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4434 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4435 elif to.this == exp.DataType.Type.TEXT: 4436 transformed = exp.TimeToStr(this=value, format=fmt) 4437 4438 if not transformed: 4439 transformed = cast(this=value, to=to, safe=safe) 4440 4441 return self.sql(transformed) 4442 4443 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4444 this = expression.this 4445 if isinstance(this, exp.JSONPathWildcard): 4446 this = self.json_path_part(this) 4447 return f".{this}" if this else "" 4448 4449 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4450 return f".{this}" 4451 4452 this = self.json_path_part(this) 4453 return ( 4454 f"[{this}]" 4455 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4456 else f".{this}" 4457 ) 4458 4459 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4460 this = self.json_path_part(expression.this) 4461 return f"[{this}]" if this else "" 4462 4463 def _simplify_unless_literal(self, expression: E) -> E: 4464 if not isinstance(expression, exp.Literal): 4465 from sqlglot.optimizer.simplify import simplify 4466 4467 expression = simplify(expression, dialect=self.dialect) 4468 4469 return expression 4470 4471 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4472 this = expression.this 4473 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4474 self.unsupported( 4475 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4476 ) 4477 return self.sql(this) 4478 4479 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4480 # The first modifier here will be the one closest to the AggFunc's arg 4481 mods = sorted( 4482 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4483 key=lambda x: 0 4484 if isinstance(x, exp.HavingMax) 4485 else (1 if isinstance(x, exp.Order) else 2), 4486 ) 4487 4488 if mods: 4489 mod = mods[0] 4490 this = expression.__class__(this=mod.this.copy()) 4491 this.meta["inline"] = True 4492 mod.this.replace(this) 4493 return self.sql(expression.this) 4494 4495 agg_func = expression.find(exp.AggFunc) 4496 4497 if agg_func: 4498 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4499 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4500 4501 return f"{self.sql(expression, 'this')} {text}" 4502 4503 def _replace_line_breaks(self, string: str) -> str: 4504 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4505 if self.pretty: 4506 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4507 return string 4508 4509 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4510 option = self.sql(expression, "this") 4511 4512 if expression.expressions: 4513 upper = option.upper() 4514 4515 # Snowflake FILE_FORMAT options are separated by whitespace 4516 sep = " " if upper == "FILE_FORMAT" else ", " 4517 4518 # Databricks copy/format options do not set their list of values with EQ 4519 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4520 values = self.expressions(expression, flat=True, sep=sep) 4521 return f"{option}{op}({values})" 4522 4523 value = self.sql(expression, "expression") 4524 4525 if not value: 4526 return option 4527 4528 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4529 4530 return f"{option}{op}{value}" 4531 4532 def credentials_sql(self, expression: exp.Credentials) -> str: 4533 cred_expr = expression.args.get("credentials") 4534 if isinstance(cred_expr, exp.Literal): 4535 # Redshift case: CREDENTIALS <string> 4536 credentials = self.sql(expression, "credentials") 4537 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4538 else: 4539 # Snowflake case: CREDENTIALS = (...) 4540 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4541 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4542 4543 storage = self.sql(expression, "storage") 4544 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4545 4546 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4547 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4548 4549 iam_role = self.sql(expression, "iam_role") 4550 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4551 4552 region = self.sql(expression, "region") 4553 region = f" REGION {region}" if region else "" 4554 4555 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4556 4557 def copy_sql(self, expression: exp.Copy) -> str: 4558 this = self.sql(expression, "this") 4559 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4560 4561 credentials = self.sql(expression, "credentials") 4562 credentials = self.seg(credentials) if credentials else "" 4563 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4564 files = self.expressions(expression, key="files", flat=True) 4565 4566 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4567 params = self.expressions( 4568 expression, 4569 key="params", 4570 sep=sep, 4571 new_line=True, 4572 skip_last=True, 4573 skip_first=True, 4574 indent=self.COPY_PARAMS_ARE_WRAPPED, 4575 ) 4576 4577 if params: 4578 if self.COPY_PARAMS_ARE_WRAPPED: 4579 params = f" WITH ({params})" 4580 elif not self.pretty: 4581 params = f" {params}" 4582 4583 return f"COPY{this}{kind} {files}{credentials}{params}" 4584 4585 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4586 return "" 4587 4588 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4589 on_sql = "ON" if expression.args.get("on") else "OFF" 4590 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4591 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4592 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4593 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4594 4595 if filter_col or retention_period: 4596 on_sql = self.func("ON", filter_col, retention_period) 4597 4598 return f"DATA_DELETION={on_sql}" 4599 4600 def maskingpolicycolumnconstraint_sql( 4601 self, expression: exp.MaskingPolicyColumnConstraint 4602 ) -> str: 4603 this = self.sql(expression, "this") 4604 expressions = self.expressions(expression, flat=True) 4605 expressions = f" USING ({expressions})" if expressions else "" 4606 return f"MASKING POLICY {this}{expressions}" 4607 4608 def gapfill_sql(self, expression: exp.GapFill) -> str: 4609 this = self.sql(expression, "this") 4610 this = f"TABLE {this}" 4611 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4612 4613 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4614 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4615 4616 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4617 this = self.sql(expression, "this") 4618 expr = expression.expression 4619 4620 if isinstance(expr, exp.Func): 4621 # T-SQL's CLR functions are case sensitive 4622 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4623 else: 4624 expr = self.sql(expression, "expression") 4625 4626 return self.scope_resolution(expr, this) 4627 4628 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4629 if self.PARSE_JSON_NAME is None: 4630 return self.sql(expression.this) 4631 4632 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4633 4634 def rand_sql(self, expression: exp.Rand) -> str: 4635 lower = self.sql(expression, "lower") 4636 upper = self.sql(expression, "upper") 4637 4638 if lower and upper: 4639 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4640 return self.func("RAND", expression.this) 4641 4642 def changes_sql(self, expression: exp.Changes) -> str: 4643 information = self.sql(expression, "information") 4644 information = f"INFORMATION => {information}" 4645 at_before = self.sql(expression, "at_before") 4646 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4647 end = self.sql(expression, "end") 4648 end = f"{self.seg('')}{end}" if end else "" 4649 4650 return f"CHANGES ({information}){at_before}{end}" 4651 4652 def pad_sql(self, expression: exp.Pad) -> str: 4653 prefix = "L" if expression.args.get("is_left") else "R" 4654 4655 fill_pattern = self.sql(expression, "fill_pattern") or None 4656 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4657 fill_pattern = "' '" 4658 4659 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4660 4661 def summarize_sql(self, expression: exp.Summarize) -> str: 4662 table = " TABLE" if expression.args.get("table") else "" 4663 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4664 4665 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4666 generate_series = exp.GenerateSeries(**expression.args) 4667 4668 parent = expression.parent 4669 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4670 parent = parent.parent 4671 4672 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4673 return self.sql(exp.Unnest(expressions=[generate_series])) 4674 4675 if isinstance(parent, exp.Select): 4676 self.unsupported("GenerateSeries projection unnesting is not supported.") 4677 4678 return self.sql(generate_series) 4679 4680 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4681 exprs = expression.expressions 4682 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4683 if len(exprs) == 0: 4684 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4685 else: 4686 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4687 else: 4688 rhs = self.expressions(expression) # type: ignore 4689 4690 return self.func(name, expression.this, rhs or None) 4691 4692 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4693 if self.SUPPORTS_CONVERT_TIMEZONE: 4694 return self.function_fallback_sql(expression) 4695 4696 source_tz = expression.args.get("source_tz") 4697 target_tz = expression.args.get("target_tz") 4698 timestamp = expression.args.get("timestamp") 4699 4700 if source_tz and timestamp: 4701 timestamp = exp.AtTimeZone( 4702 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4703 ) 4704 4705 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4706 4707 return self.sql(expr) 4708 4709 def json_sql(self, expression: exp.JSON) -> str: 4710 this = self.sql(expression, "this") 4711 this = f" {this}" if this else "" 4712 4713 _with = expression.args.get("with") 4714 4715 if _with is None: 4716 with_sql = "" 4717 elif not _with: 4718 with_sql = " WITHOUT" 4719 else: 4720 with_sql = " WITH" 4721 4722 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4723 4724 return f"JSON{this}{with_sql}{unique_sql}" 4725 4726 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4727 def _generate_on_options(arg: t.Any) -> str: 4728 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4729 4730 path = self.sql(expression, "path") 4731 returning = self.sql(expression, "returning") 4732 returning = f" RETURNING {returning}" if returning else "" 4733 4734 on_condition = self.sql(expression, "on_condition") 4735 on_condition = f" {on_condition}" if on_condition else "" 4736 4737 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4738 4739 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4740 else_ = "ELSE " if expression.args.get("else_") else "" 4741 condition = self.sql(expression, "expression") 4742 condition = f"WHEN {condition} THEN " if condition else else_ 4743 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4744 return f"{condition}{insert}" 4745 4746 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4747 kind = self.sql(expression, "kind") 4748 expressions = self.seg(self.expressions(expression, sep=" ")) 4749 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4750 return res 4751 4752 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4753 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4754 empty = expression.args.get("empty") 4755 empty = ( 4756 f"DEFAULT {empty} ON EMPTY" 4757 if isinstance(empty, exp.Expression) 4758 else self.sql(expression, "empty") 4759 ) 4760 4761 error = expression.args.get("error") 4762 error = ( 4763 f"DEFAULT {error} ON ERROR" 4764 if isinstance(error, exp.Expression) 4765 else self.sql(expression, "error") 4766 ) 4767 4768 if error and empty: 4769 error = ( 4770 f"{empty} {error}" 4771 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4772 else f"{error} {empty}" 4773 ) 4774 empty = "" 4775 4776 null = self.sql(expression, "null") 4777 4778 return f"{empty}{error}{null}" 4779 4780 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4781 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4782 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4783 4784 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4785 this = self.sql(expression, "this") 4786 path = self.sql(expression, "path") 4787 4788 passing = self.expressions(expression, "passing") 4789 passing = f" PASSING {passing}" if passing else "" 4790 4791 on_condition = self.sql(expression, "on_condition") 4792 on_condition = f" {on_condition}" if on_condition else "" 4793 4794 path = f"{path}{passing}{on_condition}" 4795 4796 return self.func("JSON_EXISTS", this, path) 4797 4798 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4799 array_agg = self.function_fallback_sql(expression) 4800 4801 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4802 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4803 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4804 parent = expression.parent 4805 if isinstance(parent, exp.Filter): 4806 parent_cond = parent.expression.this 4807 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4808 else: 4809 this = expression.this 4810 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4811 if this.find(exp.Column): 4812 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4813 this_sql = ( 4814 self.expressions(this) 4815 if isinstance(this, exp.Distinct) 4816 else self.sql(expression, "this") 4817 ) 4818 4819 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4820 4821 return array_agg 4822 4823 def apply_sql(self, expression: exp.Apply) -> str: 4824 this = self.sql(expression, "this") 4825 expr = self.sql(expression, "expression") 4826 4827 return f"{this} APPLY({expr})" 4828 4829 def _grant_or_revoke_sql( 4830 self, 4831 expression: exp.Grant | exp.Revoke, 4832 keyword: str, 4833 preposition: str, 4834 grant_option_prefix: str = "", 4835 grant_option_suffix: str = "", 4836 ) -> str: 4837 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4838 4839 kind = self.sql(expression, "kind") 4840 kind = f" {kind}" if kind else "" 4841 4842 securable = self.sql(expression, "securable") 4843 securable = f" {securable}" if securable else "" 4844 4845 principals = self.expressions(expression, key="principals", flat=True) 4846 4847 if not expression.args.get("grant_option"): 4848 grant_option_prefix = grant_option_suffix = "" 4849 4850 # cascade for revoke only 4851 cascade = self.sql(expression, "cascade") 4852 cascade = f" {cascade}" if cascade else "" 4853 4854 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4855 4856 def grant_sql(self, expression: exp.Grant) -> str: 4857 return self._grant_or_revoke_sql( 4858 expression, 4859 keyword="GRANT", 4860 preposition="TO", 4861 grant_option_suffix=" WITH GRANT OPTION", 4862 ) 4863 4864 def revoke_sql(self, expression: exp.Revoke) -> str: 4865 return self._grant_or_revoke_sql( 4866 expression, 4867 keyword="REVOKE", 4868 preposition="FROM", 4869 grant_option_prefix="GRANT OPTION FOR ", 4870 ) 4871 4872 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4873 this = self.sql(expression, "this") 4874 columns = self.expressions(expression, flat=True) 4875 columns = f"({columns})" if columns else "" 4876 4877 return f"{this}{columns}" 4878 4879 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4880 this = self.sql(expression, "this") 4881 4882 kind = self.sql(expression, "kind") 4883 kind = f"{kind} " if kind else "" 4884 4885 return f"{kind}{this}" 4886 4887 def columns_sql(self, expression: exp.Columns): 4888 func = self.function_fallback_sql(expression) 4889 if expression.args.get("unpack"): 4890 func = f"*{func}" 4891 4892 return func 4893 4894 def overlay_sql(self, expression: exp.Overlay): 4895 this = self.sql(expression, "this") 4896 expr = self.sql(expression, "expression") 4897 from_sql = self.sql(expression, "from") 4898 for_sql = self.sql(expression, "for") 4899 for_sql = f" FOR {for_sql}" if for_sql else "" 4900 4901 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4902 4903 @unsupported_args("format") 4904 def todouble_sql(self, expression: exp.ToDouble) -> str: 4905 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4906 4907 def string_sql(self, expression: exp.String) -> str: 4908 this = expression.this 4909 zone = expression.args.get("zone") 4910 4911 if zone: 4912 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4913 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4914 # set for source_tz to transpile the time conversion before the STRING cast 4915 this = exp.ConvertTimezone( 4916 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4917 ) 4918 4919 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4920 4921 def median_sql(self, expression: exp.Median): 4922 if not self.SUPPORTS_MEDIAN: 4923 return self.sql( 4924 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4925 ) 4926 4927 return self.function_fallback_sql(expression) 4928 4929 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4930 filler = self.sql(expression, "this") 4931 filler = f" {filler}" if filler else "" 4932 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4933 return f"TRUNCATE{filler} {with_count}" 4934 4935 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4936 if self.SUPPORTS_UNIX_SECONDS: 4937 return self.function_fallback_sql(expression) 4938 4939 start_ts = exp.cast( 4940 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4941 ) 4942 4943 return self.sql( 4944 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4945 ) 4946 4947 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4948 dim = expression.expression 4949 4950 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4951 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4952 if not (dim.is_int and dim.name == "1"): 4953 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4954 dim = None 4955 4956 # If dimension is required but not specified, default initialize it 4957 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4958 dim = exp.Literal.number(1) 4959 4960 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4961 4962 def attach_sql(self, expression: exp.Attach) -> str: 4963 this = self.sql(expression, "this") 4964 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4965 expressions = self.expressions(expression) 4966 expressions = f" ({expressions})" if expressions else "" 4967 4968 return f"ATTACH{exists_sql} {this}{expressions}" 4969 4970 def detach_sql(self, expression: exp.Detach) -> str: 4971 this = self.sql(expression, "this") 4972 # the DATABASE keyword is required if IF EXISTS is set 4973 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4974 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4975 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4976 4977 return f"DETACH{exists_sql} {this}" 4978 4979 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4980 this = self.sql(expression, "this") 4981 value = self.sql(expression, "expression") 4982 value = f" {value}" if value else "" 4983 return f"{this}{value}" 4984 4985 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4986 return ( 4987 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4988 ) 4989 4990 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4991 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4992 encode = f"{encode} {self.sql(expression, 'this')}" 4993 4994 properties = expression.args.get("properties") 4995 if properties: 4996 encode = f"{encode} {self.properties(properties)}" 4997 4998 return encode 4999 5000 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5001 this = self.sql(expression, "this") 5002 include = f"INCLUDE {this}" 5003 5004 column_def = self.sql(expression, "column_def") 5005 if column_def: 5006 include = f"{include} {column_def}" 5007 5008 alias = self.sql(expression, "alias") 5009 if alias: 5010 include = f"{include} AS {alias}" 5011 5012 return include 5013 5014 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5015 name = f"NAME {self.sql(expression, 'this')}" 5016 return self.func("XMLELEMENT", name, *expression.expressions) 5017 5018 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5019 this = self.sql(expression, "this") 5020 expr = self.sql(expression, "expression") 5021 expr = f"({expr})" if expr else "" 5022 return f"{this}{expr}" 5023 5024 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5025 partitions = self.expressions(expression, "partition_expressions") 5026 create = self.expressions(expression, "create_expressions") 5027 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5028 5029 def partitionbyrangepropertydynamic_sql( 5030 self, expression: exp.PartitionByRangePropertyDynamic 5031 ) -> str: 5032 start = self.sql(expression, "start") 5033 end = self.sql(expression, "end") 5034 5035 every = expression.args["every"] 5036 if isinstance(every, exp.Interval) and every.this.is_string: 5037 every.this.replace(exp.Literal.number(every.name)) 5038 5039 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5040 5041 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5042 name = self.sql(expression, "this") 5043 values = self.expressions(expression, flat=True) 5044 5045 return f"NAME {name} VALUE {values}" 5046 5047 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5048 kind = self.sql(expression, "kind") 5049 sample = self.sql(expression, "sample") 5050 return f"SAMPLE {sample} {kind}" 5051 5052 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5053 kind = self.sql(expression, "kind") 5054 option = self.sql(expression, "option") 5055 option = f" {option}" if option else "" 5056 this = self.sql(expression, "this") 5057 this = f" {this}" if this else "" 5058 columns = self.expressions(expression) 5059 columns = f" {columns}" if columns else "" 5060 return f"{kind}{option} STATISTICS{this}{columns}" 5061 5062 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5063 this = self.sql(expression, "this") 5064 columns = self.expressions(expression) 5065 inner_expression = self.sql(expression, "expression") 5066 inner_expression = f" {inner_expression}" if inner_expression else "" 5067 update_options = self.sql(expression, "update_options") 5068 update_options = f" {update_options} UPDATE" if update_options else "" 5069 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5070 5071 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5072 kind = self.sql(expression, "kind") 5073 kind = f" {kind}" if kind else "" 5074 return f"DELETE{kind} STATISTICS" 5075 5076 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5077 inner_expression = self.sql(expression, "expression") 5078 return f"LIST CHAINED ROWS{inner_expression}" 5079 5080 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5081 kind = self.sql(expression, "kind") 5082 this = self.sql(expression, "this") 5083 this = f" {this}" if this else "" 5084 inner_expression = self.sql(expression, "expression") 5085 return f"VALIDATE {kind}{this}{inner_expression}" 5086 5087 def analyze_sql(self, expression: exp.Analyze) -> str: 5088 options = self.expressions(expression, key="options", sep=" ") 5089 options = f" {options}" if options else "" 5090 kind = self.sql(expression, "kind") 5091 kind = f" {kind}" if kind else "" 5092 this = self.sql(expression, "this") 5093 this = f" {this}" if this else "" 5094 mode = self.sql(expression, "mode") 5095 mode = f" {mode}" if mode else "" 5096 properties = self.sql(expression, "properties") 5097 properties = f" {properties}" if properties else "" 5098 partition = self.sql(expression, "partition") 5099 partition = f" {partition}" if partition else "" 5100 inner_expression = self.sql(expression, "expression") 5101 inner_expression = f" {inner_expression}" if inner_expression else "" 5102 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5103 5104 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5105 this = self.sql(expression, "this") 5106 namespaces = self.expressions(expression, key="namespaces") 5107 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5108 passing = self.expressions(expression, key="passing") 5109 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5110 columns = self.expressions(expression, key="columns") 5111 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5112 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5113 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5114 5115 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5116 this = self.sql(expression, "this") 5117 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5118 5119 def export_sql(self, expression: exp.Export) -> str: 5120 this = self.sql(expression, "this") 5121 connection = self.sql(expression, "connection") 5122 connection = f"WITH CONNECTION {connection} " if connection else "" 5123 options = self.sql(expression, "options") 5124 return f"EXPORT DATA {connection}{options} AS {this}" 5125 5126 def declare_sql(self, expression: exp.Declare) -> str: 5127 return f"DECLARE {self.expressions(expression, flat=True)}" 5128 5129 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5130 variable = self.sql(expression, "this") 5131 default = self.sql(expression, "default") 5132 default = f" = {default}" if default else "" 5133 5134 kind = self.sql(expression, "kind") 5135 if isinstance(expression.args.get("kind"), exp.Schema): 5136 kind = f"TABLE {kind}" 5137 5138 return f"{variable} AS {kind}{default}" 5139 5140 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5141 kind = self.sql(expression, "kind") 5142 this = self.sql(expression, "this") 5143 set = self.sql(expression, "expression") 5144 using = self.sql(expression, "using") 5145 using = f" USING {using}" if using else "" 5146 5147 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5148 5149 return f"{kind_sql} {this} SET {set}{using}" 5150 5151 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5152 params = self.expressions(expression, key="params", flat=True) 5153 return self.func(expression.name, *expression.expressions) + f"({params})" 5154 5155 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5156 return self.func(expression.name, *expression.expressions) 5157 5158 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5159 return self.anonymousaggfunc_sql(expression) 5160 5161 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5162 return self.parameterizedagg_sql(expression) 5163 5164 def show_sql(self, expression: exp.Show) -> str: 5165 self.unsupported("Unsupported SHOW statement") 5166 return "" 5167 5168 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5169 # Snowflake GET/PUT statements: 5170 # PUT <file> <internalStage> <properties> 5171 # GET <internalStage> <file> <properties> 5172 props = expression.args.get("properties") 5173 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5174 this = self.sql(expression, "this") 5175 target = self.sql(expression, "target") 5176 5177 if isinstance(expression, exp.Put): 5178 return f"PUT {this} {target}{props_sql}" 5179 else: 5180 return f"GET {target} {this}{props_sql}" 5181 5182 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5183 this = self.sql(expression, "this") 5184 expr = self.sql(expression, "expression") 5185 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5186 return f"TRANSLATE({this} USING {expr}{with_error})" 5187 5188 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5189 if self.SUPPORTS_DECODE_CASE: 5190 return self.func("DECODE", *expression.expressions) 5191 5192 expression, *expressions = expression.expressions 5193 5194 ifs = [] 5195 for search, result in zip(expressions[::2], expressions[1::2]): 5196 if isinstance(search, exp.Literal): 5197 ifs.append(exp.If(this=expression.eq(search), true=result)) 5198 elif isinstance(search, exp.Null): 5199 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5200 else: 5201 if isinstance(search, exp.Binary): 5202 search = exp.paren(search) 5203 5204 cond = exp.or_( 5205 expression.eq(search), 5206 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5207 copy=False, 5208 ) 5209 ifs.append(exp.If(this=cond, true=result)) 5210 5211 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5212 return self.sql(case) 5213 5214 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5215 this = self.sql(expression, "this") 5216 this = self.seg(this, sep="") 5217 dimensions = self.expressions( 5218 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5219 ) 5220 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5221 metrics = self.expressions( 5222 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5223 ) 5224 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5225 where = self.sql(expression, "where") 5226 where = self.seg(f"WHERE {where}") if where else "" 5227 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5228 5229 def getextract_sql(self, expression: exp.GetExtract) -> str: 5230 this = expression.this 5231 expr = expression.expression 5232 5233 if not this.type or not expression.type: 5234 from sqlglot.optimizer.annotate_types import annotate_types 5235 5236 this = annotate_types(this, dialect=self.dialect) 5237 5238 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5239 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5240 5241 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5242 5243 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5244 return self.sql( 5245 exp.DateAdd( 5246 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5247 expression=expression.this, 5248 unit=exp.var("DAY"), 5249 ) 5250 ) 5251 5252 def space_sql(self: Generator, expression: exp.Space) -> str: 5253 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5254 5255 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5256 return f"BUILD {self.sql(expression, 'this')}" 5257 5258 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5259 method = self.sql(expression, "method") 5260 kind = expression.args.get("kind") 5261 if not kind: 5262 return f"REFRESH {method}" 5263 5264 every = self.sql(expression, "every") 5265 unit = self.sql(expression, "unit") 5266 every = f" EVERY {every} {unit}" if every else "" 5267 starts = self.sql(expression, "starts") 5268 starts = f" STARTS {starts}" if starts else "" 5269 5270 return f"REFRESH {method} ON {kind}{every}{starts}"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
729 def __init__( 730 self, 731 pretty: t.Optional[bool] = None, 732 identify: str | bool = False, 733 normalize: bool = False, 734 pad: int = 2, 735 indent: int = 2, 736 normalize_functions: t.Optional[str | bool] = None, 737 unsupported_level: ErrorLevel = ErrorLevel.WARN, 738 max_unsupported: int = 3, 739 leading_comma: bool = False, 740 max_text_width: int = 80, 741 comments: bool = True, 742 dialect: DialectType = None, 743 ): 744 import sqlglot 745 from sqlglot.dialects import Dialect 746 747 self.pretty = pretty if pretty is not None else sqlglot.pretty 748 self.identify = identify 749 self.normalize = normalize 750 self.pad = pad 751 self._indent = indent 752 self.unsupported_level = unsupported_level 753 self.max_unsupported = max_unsupported 754 self.leading_comma = leading_comma 755 self.max_text_width = max_text_width 756 self.comments = comments 757 self.dialect = Dialect.get_or_raise(dialect) 758 759 # This is both a Dialect property and a Generator argument, so we prioritize the latter 760 self.normalize_functions = ( 761 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 762 ) 763 764 self.unsupported_messages: t.List[str] = [] 765 self._escaped_quote_end: str = ( 766 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 767 ) 768 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 769 770 self._next_name = name_sequence("_t") 771 772 self._identifier_start = self.dialect.IDENTIFIER_START 773 self._identifier_end = self.dialect.IDENTIFIER_END 774 775 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WeekStart'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NCHAR: 'NCHAR'>}
777 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 778 """ 779 Generates the SQL string corresponding to the given syntax tree. 780 781 Args: 782 expression: The syntax tree. 783 copy: Whether to copy the expression. The generator performs mutations so 784 it is safer to copy. 785 786 Returns: 787 The SQL string corresponding to `expression`. 788 """ 789 if copy: 790 expression = expression.copy() 791 792 expression = self.preprocess(expression) 793 794 self.unsupported_messages = [] 795 sql = self.sql(expression).strip() 796 797 if self.pretty: 798 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 799 800 if self.unsupported_level == ErrorLevel.IGNORE: 801 return sql 802 803 if self.unsupported_level == ErrorLevel.WARN: 804 for msg in self.unsupported_messages: 805 logger.warning(msg) 806 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 807 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 808 809 return sql
Generates the SQL string corresponding to the given syntax tree.
Arguments:
- expression: The syntax tree.
- copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:
The SQL string corresponding to
expression.
def
preprocess( self, expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression:
811 def preprocess(self, expression: exp.Expression) -> exp.Expression: 812 """Apply generic preprocessing transformations to a given expression.""" 813 expression = self._move_ctes_to_top_level(expression) 814 815 if self.ENSURE_BOOLS: 816 from sqlglot.transforms import ensure_bools 817 818 expression = ensure_bools(expression) 819 820 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
844 def sanitize_comment(self, comment: str) -> str: 845 comment = " " + comment if comment[0].strip() else comment 846 comment = comment + " " if comment[-1].strip() else comment 847 848 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 849 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 850 comment = comment.replace("*/", "* /") 851 852 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
854 def maybe_comment( 855 self, 856 sql: str, 857 expression: t.Optional[exp.Expression] = None, 858 comments: t.Optional[t.List[str]] = None, 859 separated: bool = False, 860 ) -> str: 861 comments = ( 862 ((expression and expression.comments) if comments is None else comments) # type: ignore 863 if self.comments 864 else None 865 ) 866 867 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 868 return sql 869 870 comments_sql = " ".join( 871 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 872 ) 873 874 if not comments_sql: 875 return sql 876 877 comments_sql = self._replace_line_breaks(comments_sql) 878 879 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 880 return ( 881 f"{self.sep()}{comments_sql}{sql}" 882 if not sql or sql[0].isspace() 883 else f"{comments_sql}{self.sep()}{sql}" 884 ) 885 886 return f"{sql} {comments_sql}"
888 def wrap(self, expression: exp.Expression | str) -> str: 889 this_sql = ( 890 self.sql(expression) 891 if isinstance(expression, exp.UNWRAPPED_QUERIES) 892 else self.sql(expression, "this") 893 ) 894 if not this_sql: 895 return "()" 896 897 this_sql = self.indent(this_sql, level=1, pad=0) 898 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
914 def indent( 915 self, 916 sql: str, 917 level: int = 0, 918 pad: t.Optional[int] = None, 919 skip_first: bool = False, 920 skip_last: bool = False, 921 ) -> str: 922 if not self.pretty or not sql: 923 return sql 924 925 pad = self.pad if pad is None else pad 926 lines = sql.split("\n") 927 928 return "\n".join( 929 ( 930 line 931 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 932 else f"{' ' * (level * self._indent + pad)}{line}" 933 ) 934 for i, line in enumerate(lines) 935 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
937 def sql( 938 self, 939 expression: t.Optional[str | exp.Expression], 940 key: t.Optional[str] = None, 941 comment: bool = True, 942 ) -> str: 943 if not expression: 944 return "" 945 946 if isinstance(expression, str): 947 return expression 948 949 if key: 950 value = expression.args.get(key) 951 if value: 952 return self.sql(value) 953 return "" 954 955 transform = self.TRANSFORMS.get(expression.__class__) 956 957 if callable(transform): 958 sql = transform(self, expression) 959 elif isinstance(expression, exp.Expression): 960 exp_handler_name = f"{expression.key}_sql" 961 962 if hasattr(self, exp_handler_name): 963 sql = getattr(self, exp_handler_name)(expression) 964 elif isinstance(expression, exp.Func): 965 sql = self.function_fallback_sql(expression) 966 elif isinstance(expression, exp.Property): 967 sql = self.property_sql(expression) 968 else: 969 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 970 else: 971 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 972 973 return self.maybe_comment(sql, expression) if self.comments and comment else sql
980 def cache_sql(self, expression: exp.Cache) -> str: 981 lazy = " LAZY" if expression.args.get("lazy") else "" 982 table = self.sql(expression, "this") 983 options = expression.args.get("options") 984 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 985 sql = self.sql(expression, "expression") 986 sql = f" AS{self.sep()}{sql}" if sql else "" 987 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 988 return self.prepend_ctes(expression, sql)
990 def characterset_sql(self, expression: exp.CharacterSet) -> str: 991 if isinstance(expression.parent, exp.Cast): 992 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 993 default = "DEFAULT " if expression.args.get("default") else "" 994 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1008 def column_sql(self, expression: exp.Column) -> str: 1009 join_mark = " (+)" if expression.args.get("join_mark") else "" 1010 1011 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1012 join_mark = "" 1013 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1014 1015 return f"{self.column_parts(expression)}{join_mark}"
1023 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1024 column = self.sql(expression, "this") 1025 kind = self.sql(expression, "kind") 1026 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1027 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1028 kind = f"{sep}{kind}" if kind else "" 1029 constraints = f" {constraints}" if constraints else "" 1030 position = self.sql(expression, "position") 1031 position = f" {position}" if position else "" 1032 1033 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1034 kind = "" 1035 1036 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1043 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1044 this = self.sql(expression, "this") 1045 if expression.args.get("not_null"): 1046 persisted = " PERSISTED NOT NULL" 1047 elif expression.args.get("persisted"): 1048 persisted = " PERSISTED" 1049 else: 1050 persisted = "" 1051 1052 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1065 def generatedasidentitycolumnconstraint_sql( 1066 self, expression: exp.GeneratedAsIdentityColumnConstraint 1067 ) -> str: 1068 this = "" 1069 if expression.this is not None: 1070 on_null = " ON NULL" if expression.args.get("on_null") else "" 1071 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1072 1073 start = expression.args.get("start") 1074 start = f"START WITH {start}" if start else "" 1075 increment = expression.args.get("increment") 1076 increment = f" INCREMENT BY {increment}" if increment else "" 1077 minvalue = expression.args.get("minvalue") 1078 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1079 maxvalue = expression.args.get("maxvalue") 1080 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1081 cycle = expression.args.get("cycle") 1082 cycle_sql = "" 1083 1084 if cycle is not None: 1085 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1086 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1087 1088 sequence_opts = "" 1089 if start or increment or cycle_sql: 1090 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1091 sequence_opts = f" ({sequence_opts.strip()})" 1092 1093 expr = self.sql(expression, "expression") 1094 expr = f"({expr})" if expr else "IDENTITY" 1095 1096 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1098 def generatedasrowcolumnconstraint_sql( 1099 self, expression: exp.GeneratedAsRowColumnConstraint 1100 ) -> str: 1101 start = "START" if expression.args.get("start") else "END" 1102 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1103 return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def
periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
def
notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
1113 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1114 desc = expression.args.get("desc") 1115 if desc is not None: 1116 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1117 options = self.expressions(expression, key="options", flat=True, sep=" ") 1118 options = f" {options}" if options else "" 1119 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1121 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1122 this = self.sql(expression, "this") 1123 this = f" {this}" if this else "" 1124 index_type = expression.args.get("index_type") 1125 index_type = f" USING {index_type}" if index_type else "" 1126 on_conflict = self.sql(expression, "on_conflict") 1127 on_conflict = f" {on_conflict}" if on_conflict else "" 1128 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1129 options = self.expressions(expression, key="options", flat=True, sep=" ") 1130 options = f" {options}" if options else "" 1131 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1136 def create_sql(self, expression: exp.Create) -> str: 1137 kind = self.sql(expression, "kind") 1138 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1139 properties = expression.args.get("properties") 1140 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1141 1142 this = self.createable_sql(expression, properties_locs) 1143 1144 properties_sql = "" 1145 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1146 exp.Properties.Location.POST_WITH 1147 ): 1148 props_ast = exp.Properties( 1149 expressions=[ 1150 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1151 *properties_locs[exp.Properties.Location.POST_WITH], 1152 ] 1153 ) 1154 props_ast.parent = expression 1155 properties_sql = self.sql(props_ast) 1156 1157 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1158 properties_sql = self.sep() + properties_sql 1159 elif not self.pretty: 1160 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1161 properties_sql = f" {properties_sql}" 1162 1163 begin = " BEGIN" if expression.args.get("begin") else "" 1164 end = " END" if expression.args.get("end") else "" 1165 1166 expression_sql = self.sql(expression, "expression") 1167 if expression_sql: 1168 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1169 1170 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1171 postalias_props_sql = "" 1172 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1173 postalias_props_sql = self.properties( 1174 exp.Properties( 1175 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1176 ), 1177 wrapped=False, 1178 ) 1179 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1180 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1181 1182 postindex_props_sql = "" 1183 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1184 postindex_props_sql = self.properties( 1185 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1186 wrapped=False, 1187 prefix=" ", 1188 ) 1189 1190 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1191 indexes = f" {indexes}" if indexes else "" 1192 index_sql = indexes + postindex_props_sql 1193 1194 replace = " OR REPLACE" if expression.args.get("replace") else "" 1195 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1196 unique = " UNIQUE" if expression.args.get("unique") else "" 1197 1198 clustered = expression.args.get("clustered") 1199 if clustered is None: 1200 clustered_sql = "" 1201 elif clustered: 1202 clustered_sql = " CLUSTERED COLUMNSTORE" 1203 else: 1204 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1205 1206 postcreate_props_sql = "" 1207 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1208 postcreate_props_sql = self.properties( 1209 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1210 sep=" ", 1211 prefix=" ", 1212 wrapped=False, 1213 ) 1214 1215 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1216 1217 postexpression_props_sql = "" 1218 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1219 postexpression_props_sql = self.properties( 1220 exp.Properties( 1221 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1222 ), 1223 sep=" ", 1224 prefix=" ", 1225 wrapped=False, 1226 ) 1227 1228 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1229 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1230 no_schema_binding = ( 1231 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1232 ) 1233 1234 clone = self.sql(expression, "clone") 1235 clone = f" {clone}" if clone else "" 1236 1237 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1238 properties_expression = f"{expression_sql}{properties_sql}" 1239 else: 1240 properties_expression = f"{properties_sql}{expression_sql}" 1241 1242 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1243 return self.prepend_ctes(expression, expression_sql)
1245 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1246 start = self.sql(expression, "start") 1247 start = f"START WITH {start}" if start else "" 1248 increment = self.sql(expression, "increment") 1249 increment = f" INCREMENT BY {increment}" if increment else "" 1250 minvalue = self.sql(expression, "minvalue") 1251 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1252 maxvalue = self.sql(expression, "maxvalue") 1253 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1254 owned = self.sql(expression, "owned") 1255 owned = f" OWNED BY {owned}" if owned else "" 1256 1257 cache = expression.args.get("cache") 1258 if cache is None: 1259 cache_str = "" 1260 elif cache is True: 1261 cache_str = " CACHE" 1262 else: 1263 cache_str = f" CACHE {cache}" 1264 1265 options = self.expressions(expression, key="options", flat=True, sep=" ") 1266 options = f" {options}" if options else "" 1267 1268 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1270 def clone_sql(self, expression: exp.Clone) -> str: 1271 this = self.sql(expression, "this") 1272 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1273 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1274 return f"{shallow}{keyword} {this}"
1276 def describe_sql(self, expression: exp.Describe) -> str: 1277 style = expression.args.get("style") 1278 style = f" {style}" if style else "" 1279 partition = self.sql(expression, "partition") 1280 partition = f" {partition}" if partition else "" 1281 format = self.sql(expression, "format") 1282 format = f" {format}" if format else "" 1283 1284 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1296 def with_sql(self, expression: exp.With) -> str: 1297 sql = self.expressions(expression, flat=True) 1298 recursive = ( 1299 "RECURSIVE " 1300 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1301 else "" 1302 ) 1303 search = self.sql(expression, "search") 1304 search = f" {search}" if search else "" 1305 1306 return f"WITH {recursive}{sql}{search}"
1308 def cte_sql(self, expression: exp.CTE) -> str: 1309 alias = expression.args.get("alias") 1310 if alias: 1311 alias.add_comments(expression.pop_comments()) 1312 1313 alias_sql = self.sql(expression, "alias") 1314 1315 materialized = expression.args.get("materialized") 1316 if materialized is False: 1317 materialized = "NOT MATERIALIZED " 1318 elif materialized: 1319 materialized = "MATERIALIZED " 1320 1321 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1323 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1324 alias = self.sql(expression, "this") 1325 columns = self.expressions(expression, key="columns", flat=True) 1326 columns = f"({columns})" if columns else "" 1327 1328 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1329 columns = "" 1330 self.unsupported("Named columns are not supported in table alias.") 1331 1332 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1333 alias = self._next_name() 1334 1335 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1343 def hexstring_sql( 1344 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1345 ) -> str: 1346 this = self.sql(expression, "this") 1347 is_integer_type = expression.args.get("is_integer") 1348 1349 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1350 not self.dialect.HEX_START and not binary_function_repr 1351 ): 1352 # Integer representation will be returned if: 1353 # - The read dialect treats the hex value as integer literal but not the write 1354 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1355 return f"{int(this, 16)}" 1356 1357 if not is_integer_type: 1358 # Read dialect treats the hex value as BINARY/BLOB 1359 if binary_function_repr: 1360 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1361 return self.func(binary_function_repr, exp.Literal.string(this)) 1362 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1363 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1364 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1365 1366 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1374 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1375 this = self.sql(expression, "this") 1376 escape = expression.args.get("escape") 1377 1378 if self.dialect.UNICODE_START: 1379 escape_substitute = r"\\\1" 1380 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1381 else: 1382 escape_substitute = r"\\u\1" 1383 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1384 1385 if escape: 1386 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1387 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1388 else: 1389 escape_pattern = ESCAPED_UNICODE_RE 1390 escape_sql = "" 1391 1392 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1393 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1394 1395 return f"{left_quote}{this}{right_quote}{escape_sql}"
1397 def rawstring_sql(self, expression: exp.RawString) -> str: 1398 string = expression.this 1399 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1400 string = string.replace("\\", "\\\\") 1401 1402 string = self.escape_str(string, escape_backslash=False) 1403 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1411 def datatype_sql(self, expression: exp.DataType) -> str: 1412 nested = "" 1413 values = "" 1414 interior = self.expressions(expression, flat=True) 1415 1416 type_value = expression.this 1417 if type_value in self.UNSUPPORTED_TYPES: 1418 self.unsupported( 1419 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1420 ) 1421 1422 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1423 type_sql = self.sql(expression, "kind") 1424 else: 1425 type_sql = ( 1426 self.TYPE_MAPPING.get(type_value, type_value.value) 1427 if isinstance(type_value, exp.DataType.Type) 1428 else type_value 1429 ) 1430 1431 if interior: 1432 if expression.args.get("nested"): 1433 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1434 if expression.args.get("values") is not None: 1435 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1436 values = self.expressions(expression, key="values", flat=True) 1437 values = f"{delimiters[0]}{values}{delimiters[1]}" 1438 elif type_value == exp.DataType.Type.INTERVAL: 1439 nested = f" {interior}" 1440 else: 1441 nested = f"({interior})" 1442 1443 type_sql = f"{type_sql}{nested}{values}" 1444 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1445 exp.DataType.Type.TIMETZ, 1446 exp.DataType.Type.TIMESTAMPTZ, 1447 ): 1448 type_sql = f"{type_sql} WITH TIME ZONE" 1449 1450 return type_sql
1452 def directory_sql(self, expression: exp.Directory) -> str: 1453 local = "LOCAL " if expression.args.get("local") else "" 1454 row_format = self.sql(expression, "row_format") 1455 row_format = f" {row_format}" if row_format else "" 1456 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1458 def delete_sql(self, expression: exp.Delete) -> str: 1459 this = self.sql(expression, "this") 1460 this = f" FROM {this}" if this else "" 1461 using = self.sql(expression, "using") 1462 using = f" USING {using}" if using else "" 1463 cluster = self.sql(expression, "cluster") 1464 cluster = f" {cluster}" if cluster else "" 1465 where = self.sql(expression, "where") 1466 returning = self.sql(expression, "returning") 1467 limit = self.sql(expression, "limit") 1468 tables = self.expressions(expression, key="tables") 1469 tables = f" {tables}" if tables else "" 1470 if self.RETURNING_END: 1471 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1472 else: 1473 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1474 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1476 def drop_sql(self, expression: exp.Drop) -> str: 1477 this = self.sql(expression, "this") 1478 expressions = self.expressions(expression, flat=True) 1479 expressions = f" ({expressions})" if expressions else "" 1480 kind = expression.args["kind"] 1481 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1482 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1483 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1484 on_cluster = self.sql(expression, "cluster") 1485 on_cluster = f" {on_cluster}" if on_cluster else "" 1486 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1487 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1488 cascade = " CASCADE" if expression.args.get("cascade") else "" 1489 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1490 purge = " PURGE" if expression.args.get("purge") else "" 1491 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1493 def set_operation(self, expression: exp.SetOperation) -> str: 1494 op_type = type(expression) 1495 op_name = op_type.key.upper() 1496 1497 distinct = expression.args.get("distinct") 1498 if ( 1499 distinct is False 1500 and op_type in (exp.Except, exp.Intersect) 1501 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1502 ): 1503 self.unsupported(f"{op_name} ALL is not supported") 1504 1505 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1506 1507 if distinct is None: 1508 distinct = default_distinct 1509 if distinct is None: 1510 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1511 1512 if distinct is default_distinct: 1513 distinct_or_all = "" 1514 else: 1515 distinct_or_all = " DISTINCT" if distinct else " ALL" 1516 1517 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1518 side_kind = f"{side_kind} " if side_kind else "" 1519 1520 by_name = " BY NAME" if expression.args.get("by_name") else "" 1521 on = self.expressions(expression, key="on", flat=True) 1522 on = f" ON ({on})" if on else "" 1523 1524 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1526 def set_operations(self, expression: exp.SetOperation) -> str: 1527 if not self.SET_OP_MODIFIERS: 1528 limit = expression.args.get("limit") 1529 order = expression.args.get("order") 1530 1531 if limit or order: 1532 select = self._move_ctes_to_top_level( 1533 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1534 ) 1535 1536 if limit: 1537 select = select.limit(limit.pop(), copy=False) 1538 if order: 1539 select = select.order_by(order.pop(), copy=False) 1540 return self.sql(select) 1541 1542 sqls: t.List[str] = [] 1543 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1544 1545 while stack: 1546 node = stack.pop() 1547 1548 if isinstance(node, exp.SetOperation): 1549 stack.append(node.expression) 1550 stack.append( 1551 self.maybe_comment( 1552 self.set_operation(node), comments=node.comments, separated=True 1553 ) 1554 ) 1555 stack.append(node.this) 1556 else: 1557 sqls.append(self.sql(node)) 1558 1559 this = self.sep().join(sqls) 1560 this = self.query_modifiers(expression, this) 1561 return self.prepend_ctes(expression, this)
1563 def fetch_sql(self, expression: exp.Fetch) -> str: 1564 direction = expression.args.get("direction") 1565 direction = f" {direction}" if direction else "" 1566 count = self.sql(expression, "count") 1567 count = f" {count}" if count else "" 1568 limit_options = self.sql(expression, "limit_options") 1569 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1570 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1572 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1573 percent = " PERCENT" if expression.args.get("percent") else "" 1574 rows = " ROWS" if expression.args.get("rows") else "" 1575 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1576 if not with_ties and rows: 1577 with_ties = " ONLY" 1578 return f"{percent}{rows}{with_ties}"
1580 def filter_sql(self, expression: exp.Filter) -> str: 1581 if self.AGGREGATE_FILTER_SUPPORTED: 1582 this = self.sql(expression, "this") 1583 where = self.sql(expression, "expression").strip() 1584 return f"{this} FILTER({where})" 1585 1586 agg = expression.this 1587 agg_arg = agg.this 1588 cond = expression.expression.this 1589 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1590 return self.sql(agg)
1599 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1600 using = self.sql(expression, "using") 1601 using = f" USING {using}" if using else "" 1602 columns = self.expressions(expression, key="columns", flat=True) 1603 columns = f"({columns})" if columns else "" 1604 partition_by = self.expressions(expression, key="partition_by", flat=True) 1605 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1606 where = self.sql(expression, "where") 1607 include = self.expressions(expression, key="include", flat=True) 1608 if include: 1609 include = f" INCLUDE ({include})" 1610 with_storage = self.expressions(expression, key="with_storage", flat=True) 1611 with_storage = f" WITH ({with_storage})" if with_storage else "" 1612 tablespace = self.sql(expression, "tablespace") 1613 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1614 on = self.sql(expression, "on") 1615 on = f" ON {on}" if on else "" 1616 1617 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1619 def index_sql(self, expression: exp.Index) -> str: 1620 unique = "UNIQUE " if expression.args.get("unique") else "" 1621 primary = "PRIMARY " if expression.args.get("primary") else "" 1622 amp = "AMP " if expression.args.get("amp") else "" 1623 name = self.sql(expression, "this") 1624 name = f"{name} " if name else "" 1625 table = self.sql(expression, "table") 1626 table = f"{self.INDEX_ON} {table}" if table else "" 1627 1628 index = "INDEX " if not table else "" 1629 1630 params = self.sql(expression, "params") 1631 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1633 def identifier_sql(self, expression: exp.Identifier) -> str: 1634 text = expression.name 1635 lower = text.lower() 1636 text = lower if self.normalize and not expression.quoted else text 1637 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1638 if ( 1639 expression.quoted 1640 or self.dialect.can_identify(text, self.identify) 1641 or lower in self.RESERVED_KEYWORDS 1642 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1643 ): 1644 text = f"{self._identifier_start}{text}{self._identifier_end}" 1645 return text
1660 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1661 input_format = self.sql(expression, "input_format") 1662 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1663 output_format = self.sql(expression, "output_format") 1664 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1665 return self.sep().join((input_format, output_format))
1675 def properties_sql(self, expression: exp.Properties) -> str: 1676 root_properties = [] 1677 with_properties = [] 1678 1679 for p in expression.expressions: 1680 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1681 if p_loc == exp.Properties.Location.POST_WITH: 1682 with_properties.append(p) 1683 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1684 root_properties.append(p) 1685 1686 root_props_ast = exp.Properties(expressions=root_properties) 1687 root_props_ast.parent = expression.parent 1688 1689 with_props_ast = exp.Properties(expressions=with_properties) 1690 with_props_ast.parent = expression.parent 1691 1692 root_props = self.root_properties(root_props_ast) 1693 with_props = self.with_properties(with_props_ast) 1694 1695 if root_props and with_props and not self.pretty: 1696 with_props = " " + with_props 1697 1698 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1705 def properties( 1706 self, 1707 properties: exp.Properties, 1708 prefix: str = "", 1709 sep: str = ", ", 1710 suffix: str = "", 1711 wrapped: bool = True, 1712 ) -> str: 1713 if properties.expressions: 1714 expressions = self.expressions(properties, sep=sep, indent=False) 1715 if expressions: 1716 expressions = self.wrap(expressions) if wrapped else expressions 1717 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1718 return ""
1723 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1724 properties_locs = defaultdict(list) 1725 for p in properties.expressions: 1726 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1727 if p_loc != exp.Properties.Location.UNSUPPORTED: 1728 properties_locs[p_loc].append(p) 1729 else: 1730 self.unsupported(f"Unsupported property {p.key}") 1731 1732 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1739 def property_sql(self, expression: exp.Property) -> str: 1740 property_cls = expression.__class__ 1741 if property_cls == exp.Property: 1742 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1743 1744 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1745 if not property_name: 1746 self.unsupported(f"Unsupported property {expression.key}") 1747 1748 return f"{property_name}={self.sql(expression, 'this')}"
1750 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1751 if self.SUPPORTS_CREATE_TABLE_LIKE: 1752 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1753 options = f" {options}" if options else "" 1754 1755 like = f"LIKE {self.sql(expression, 'this')}{options}" 1756 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1757 like = f"({like})" 1758 1759 return like 1760 1761 if expression.expressions: 1762 self.unsupported("Transpilation of LIKE property options is unsupported") 1763 1764 select = exp.select("*").from_(expression.this).limit(0) 1765 return f"AS {self.sql(select)}"
1772 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1773 no = "NO " if expression.args.get("no") else "" 1774 local = expression.args.get("local") 1775 local = f"{local} " if local else "" 1776 dual = "DUAL " if expression.args.get("dual") else "" 1777 before = "BEFORE " if expression.args.get("before") else "" 1778 after = "AFTER " if expression.args.get("after") else "" 1779 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1795 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1796 if expression.args.get("no"): 1797 return "NO MERGEBLOCKRATIO" 1798 if expression.args.get("default"): 1799 return "DEFAULT MERGEBLOCKRATIO" 1800 1801 percent = " PERCENT" if expression.args.get("percent") else "" 1802 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1804 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1805 default = expression.args.get("default") 1806 minimum = expression.args.get("minimum") 1807 maximum = expression.args.get("maximum") 1808 if default or minimum or maximum: 1809 if default: 1810 prop = "DEFAULT" 1811 elif minimum: 1812 prop = "MINIMUM" 1813 else: 1814 prop = "MAXIMUM" 1815 return f"{prop} DATABLOCKSIZE" 1816 units = expression.args.get("units") 1817 units = f" {units}" if units else "" 1818 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1820 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1821 autotemp = expression.args.get("autotemp") 1822 always = expression.args.get("always") 1823 default = expression.args.get("default") 1824 manual = expression.args.get("manual") 1825 never = expression.args.get("never") 1826 1827 if autotemp is not None: 1828 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1829 elif always: 1830 prop = "ALWAYS" 1831 elif default: 1832 prop = "DEFAULT" 1833 elif manual: 1834 prop = "MANUAL" 1835 elif never: 1836 prop = "NEVER" 1837 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1839 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1840 no = expression.args.get("no") 1841 no = " NO" if no else "" 1842 concurrent = expression.args.get("concurrent") 1843 concurrent = " CONCURRENT" if concurrent else "" 1844 target = self.sql(expression, "target") 1845 target = f" {target}" if target else "" 1846 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1848 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1849 if isinstance(expression.this, list): 1850 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1851 if expression.this: 1852 modulus = self.sql(expression, "this") 1853 remainder = self.sql(expression, "expression") 1854 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1855 1856 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1857 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1858 return f"FROM ({from_expressions}) TO ({to_expressions})"
1860 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1861 this = self.sql(expression, "this") 1862 1863 for_values_or_default = expression.expression 1864 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1865 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1866 else: 1867 for_values_or_default = " DEFAULT" 1868 1869 return f"PARTITION OF {this}{for_values_or_default}"
1871 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1872 kind = expression.args.get("kind") 1873 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1874 for_or_in = expression.args.get("for_or_in") 1875 for_or_in = f" {for_or_in}" if for_or_in else "" 1876 lock_type = expression.args.get("lock_type") 1877 override = " OVERRIDE" if expression.args.get("override") else "" 1878 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1880 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1881 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1882 statistics = expression.args.get("statistics") 1883 statistics_sql = "" 1884 if statistics is not None: 1885 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1886 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1888 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1889 this = self.sql(expression, "this") 1890 this = f"HISTORY_TABLE={this}" if this else "" 1891 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1892 data_consistency = ( 1893 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1894 ) 1895 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1896 retention_period = ( 1897 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1898 ) 1899 1900 if this: 1901 on_sql = self.func("ON", this, data_consistency, retention_period) 1902 else: 1903 on_sql = "ON" if expression.args.get("on") else "OFF" 1904 1905 sql = f"SYSTEM_VERSIONING={on_sql}" 1906 1907 return f"WITH({sql})" if expression.args.get("with") else sql
1909 def insert_sql(self, expression: exp.Insert) -> str: 1910 hint = self.sql(expression, "hint") 1911 overwrite = expression.args.get("overwrite") 1912 1913 if isinstance(expression.this, exp.Directory): 1914 this = " OVERWRITE" if overwrite else " INTO" 1915 else: 1916 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1917 1918 stored = self.sql(expression, "stored") 1919 stored = f" {stored}" if stored else "" 1920 alternative = expression.args.get("alternative") 1921 alternative = f" OR {alternative}" if alternative else "" 1922 ignore = " IGNORE" if expression.args.get("ignore") else "" 1923 is_function = expression.args.get("is_function") 1924 if is_function: 1925 this = f"{this} FUNCTION" 1926 this = f"{this} {self.sql(expression, 'this')}" 1927 1928 exists = " IF EXISTS" if expression.args.get("exists") else "" 1929 where = self.sql(expression, "where") 1930 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1931 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1932 on_conflict = self.sql(expression, "conflict") 1933 on_conflict = f" {on_conflict}" if on_conflict else "" 1934 by_name = " BY NAME" if expression.args.get("by_name") else "" 1935 returning = self.sql(expression, "returning") 1936 1937 if self.RETURNING_END: 1938 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1939 else: 1940 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1941 1942 partition_by = self.sql(expression, "partition") 1943 partition_by = f" {partition_by}" if partition_by else "" 1944 settings = self.sql(expression, "settings") 1945 settings = f" {settings}" if settings else "" 1946 1947 source = self.sql(expression, "source") 1948 source = f"TABLE {source}" if source else "" 1949 1950 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1951 return self.prepend_ctes(expression, sql)
1969 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1970 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1971 1972 constraint = self.sql(expression, "constraint") 1973 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1974 1975 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1976 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1977 action = self.sql(expression, "action") 1978 1979 expressions = self.expressions(expression, flat=True) 1980 if expressions: 1981 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1982 expressions = f" {set_keyword}{expressions}" 1983 1984 where = self.sql(expression, "where") 1985 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1990 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1991 fields = self.sql(expression, "fields") 1992 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1993 escaped = self.sql(expression, "escaped") 1994 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1995 items = self.sql(expression, "collection_items") 1996 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1997 keys = self.sql(expression, "map_keys") 1998 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1999 lines = self.sql(expression, "lines") 2000 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2001 null = self.sql(expression, "null") 2002 null = f" NULL DEFINED AS {null}" if null else "" 2003 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2031 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2032 table = self.table_parts(expression) 2033 only = "ONLY " if expression.args.get("only") else "" 2034 partition = self.sql(expression, "partition") 2035 partition = f" {partition}" if partition else "" 2036 version = self.sql(expression, "version") 2037 version = f" {version}" if version else "" 2038 alias = self.sql(expression, "alias") 2039 alias = f"{sep}{alias}" if alias else "" 2040 2041 sample = self.sql(expression, "sample") 2042 if self.dialect.ALIAS_POST_TABLESAMPLE: 2043 sample_pre_alias = sample 2044 sample_post_alias = "" 2045 else: 2046 sample_pre_alias = "" 2047 sample_post_alias = sample 2048 2049 hints = self.expressions(expression, key="hints", sep=" ") 2050 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2051 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2052 joins = self.indent( 2053 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2054 ) 2055 laterals = self.expressions(expression, key="laterals", sep="") 2056 2057 file_format = self.sql(expression, "format") 2058 if file_format: 2059 pattern = self.sql(expression, "pattern") 2060 pattern = f", PATTERN => {pattern}" if pattern else "" 2061 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2062 2063 ordinality = expression.args.get("ordinality") or "" 2064 if ordinality: 2065 ordinality = f" WITH ORDINALITY{alias}" 2066 alias = "" 2067 2068 when = self.sql(expression, "when") 2069 if when: 2070 table = f"{table} {when}" 2071 2072 changes = self.sql(expression, "changes") 2073 changes = f" {changes}" if changes else "" 2074 2075 rows_from = self.expressions(expression, key="rows_from") 2076 if rows_from: 2077 table = f"ROWS FROM {self.wrap(rows_from)}" 2078 2079 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2081 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2082 table = self.func("TABLE", expression.this) 2083 alias = self.sql(expression, "alias") 2084 alias = f" AS {alias}" if alias else "" 2085 sample = self.sql(expression, "sample") 2086 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2087 joins = self.indent( 2088 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2089 ) 2090 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2092 def tablesample_sql( 2093 self, 2094 expression: exp.TableSample, 2095 tablesample_keyword: t.Optional[str] = None, 2096 ) -> str: 2097 method = self.sql(expression, "method") 2098 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2099 numerator = self.sql(expression, "bucket_numerator") 2100 denominator = self.sql(expression, "bucket_denominator") 2101 field = self.sql(expression, "bucket_field") 2102 field = f" ON {field}" if field else "" 2103 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2104 seed = self.sql(expression, "seed") 2105 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2106 2107 size = self.sql(expression, "size") 2108 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2109 size = f"{size} ROWS" 2110 2111 percent = self.sql(expression, "percent") 2112 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2113 percent = f"{percent} PERCENT" 2114 2115 expr = f"{bucket}{percent}{size}" 2116 if self.TABLESAMPLE_REQUIRES_PARENS: 2117 expr = f"({expr})" 2118 2119 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2121 def pivot_sql(self, expression: exp.Pivot) -> str: 2122 expressions = self.expressions(expression, flat=True) 2123 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2124 2125 group = self.sql(expression, "group") 2126 2127 if expression.this: 2128 this = self.sql(expression, "this") 2129 if not expressions: 2130 return f"UNPIVOT {this}" 2131 2132 on = f"{self.seg('ON')} {expressions}" 2133 into = self.sql(expression, "into") 2134 into = f"{self.seg('INTO')} {into}" if into else "" 2135 using = self.expressions(expression, key="using", flat=True) 2136 using = f"{self.seg('USING')} {using}" if using else "" 2137 return f"{direction} {this}{on}{into}{using}{group}" 2138 2139 alias = self.sql(expression, "alias") 2140 alias = f" AS {alias}" if alias else "" 2141 2142 fields = self.expressions( 2143 expression, 2144 "fields", 2145 sep=" ", 2146 dynamic=True, 2147 new_line=True, 2148 skip_first=True, 2149 skip_last=True, 2150 ) 2151 2152 include_nulls = expression.args.get("include_nulls") 2153 if include_nulls is not None: 2154 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2155 else: 2156 nulls = "" 2157 2158 default_on_null = self.sql(expression, "default_on_null") 2159 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2160 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2171 def update_sql(self, expression: exp.Update) -> str: 2172 this = self.sql(expression, "this") 2173 set_sql = self.expressions(expression, flat=True) 2174 from_sql = self.sql(expression, "from") 2175 where_sql = self.sql(expression, "where") 2176 returning = self.sql(expression, "returning") 2177 order = self.sql(expression, "order") 2178 limit = self.sql(expression, "limit") 2179 if self.RETURNING_END: 2180 expression_sql = f"{from_sql}{where_sql}{returning}" 2181 else: 2182 expression_sql = f"{returning}{from_sql}{where_sql}" 2183 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2184 return self.prepend_ctes(expression, sql)
2186 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2187 values_as_table = values_as_table and self.VALUES_AS_TABLE 2188 2189 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2190 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2191 args = self.expressions(expression) 2192 alias = self.sql(expression, "alias") 2193 values = f"VALUES{self.seg('')}{args}" 2194 values = ( 2195 f"({values})" 2196 if self.WRAP_DERIVED_VALUES 2197 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2198 else values 2199 ) 2200 return f"{values} AS {alias}" if alias else values 2201 2202 # Converts `VALUES...` expression into a series of select unions. 2203 alias_node = expression.args.get("alias") 2204 column_names = alias_node and alias_node.columns 2205 2206 selects: t.List[exp.Query] = [] 2207 2208 for i, tup in enumerate(expression.expressions): 2209 row = tup.expressions 2210 2211 if i == 0 and column_names: 2212 row = [ 2213 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2214 ] 2215 2216 selects.append(exp.Select(expressions=row)) 2217 2218 if self.pretty: 2219 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2220 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2221 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2222 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2223 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2224 2225 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2226 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2227 return f"({unions}){alias}"
2232 @unsupported_args("expressions") 2233 def into_sql(self, expression: exp.Into) -> str: 2234 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2235 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2236 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2253 def group_sql(self, expression: exp.Group) -> str: 2254 group_by_all = expression.args.get("all") 2255 if group_by_all is True: 2256 modifier = " ALL" 2257 elif group_by_all is False: 2258 modifier = " DISTINCT" 2259 else: 2260 modifier = "" 2261 2262 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2263 2264 grouping_sets = self.expressions(expression, key="grouping_sets") 2265 cube = self.expressions(expression, key="cube") 2266 rollup = self.expressions(expression, key="rollup") 2267 2268 groupings = csv( 2269 self.seg(grouping_sets) if grouping_sets else "", 2270 self.seg(cube) if cube else "", 2271 self.seg(rollup) if rollup else "", 2272 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2273 sep=self.GROUPINGS_SEP, 2274 ) 2275 2276 if ( 2277 expression.expressions 2278 and groupings 2279 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2280 ): 2281 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2282 2283 return f"{group_by}{groupings}"
2289 def connect_sql(self, expression: exp.Connect) -> str: 2290 start = self.sql(expression, "start") 2291 start = self.seg(f"START WITH {start}") if start else "" 2292 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2293 connect = self.sql(expression, "connect") 2294 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2295 return start + connect
2300 def join_sql(self, expression: exp.Join) -> str: 2301 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2302 side = None 2303 else: 2304 side = expression.side 2305 2306 op_sql = " ".join( 2307 op 2308 for op in ( 2309 expression.method, 2310 "GLOBAL" if expression.args.get("global") else None, 2311 side, 2312 expression.kind, 2313 expression.hint if self.JOIN_HINTS else None, 2314 ) 2315 if op 2316 ) 2317 match_cond = self.sql(expression, "match_condition") 2318 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2319 on_sql = self.sql(expression, "on") 2320 using = expression.args.get("using") 2321 2322 if not on_sql and using: 2323 on_sql = csv(*(self.sql(column) for column in using)) 2324 2325 this = expression.this 2326 this_sql = self.sql(this) 2327 2328 exprs = self.expressions(expression) 2329 if exprs: 2330 this_sql = f"{this_sql},{self.seg(exprs)}" 2331 2332 if on_sql: 2333 on_sql = self.indent(on_sql, skip_first=True) 2334 space = self.seg(" " * self.pad) if self.pretty else " " 2335 if using: 2336 on_sql = f"{space}USING ({on_sql})" 2337 else: 2338 on_sql = f"{space}ON {on_sql}" 2339 elif not op_sql: 2340 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2341 return f" {this_sql}" 2342 2343 return f", {this_sql}" 2344 2345 if op_sql != "STRAIGHT_JOIN": 2346 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2347 2348 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2349 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def
lambda_sql( self, expression: sqlglot.expressions.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2356 def lateral_op(self, expression: exp.Lateral) -> str: 2357 cross_apply = expression.args.get("cross_apply") 2358 2359 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2360 if cross_apply is True: 2361 op = "INNER JOIN " 2362 elif cross_apply is False: 2363 op = "LEFT JOIN " 2364 else: 2365 op = "" 2366 2367 return f"{op}LATERAL"
2369 def lateral_sql(self, expression: exp.Lateral) -> str: 2370 this = self.sql(expression, "this") 2371 2372 if expression.args.get("view"): 2373 alias = expression.args["alias"] 2374 columns = self.expressions(alias, key="columns", flat=True) 2375 table = f" {alias.name}" if alias.name else "" 2376 columns = f" AS {columns}" if columns else "" 2377 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2378 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2379 2380 alias = self.sql(expression, "alias") 2381 alias = f" AS {alias}" if alias else "" 2382 2383 ordinality = expression.args.get("ordinality") or "" 2384 if ordinality: 2385 ordinality = f" WITH ORDINALITY{alias}" 2386 alias = "" 2387 2388 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2390 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2391 this = self.sql(expression, "this") 2392 2393 args = [ 2394 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2395 for e in (expression.args.get(k) for k in ("offset", "expression")) 2396 if e 2397 ] 2398 2399 args_sql = ", ".join(self.sql(e) for e in args) 2400 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2401 expressions = self.expressions(expression, flat=True) 2402 limit_options = self.sql(expression, "limit_options") 2403 expressions = f" BY {expressions}" if expressions else "" 2404 2405 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2407 def offset_sql(self, expression: exp.Offset) -> str: 2408 this = self.sql(expression, "this") 2409 value = expression.expression 2410 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2411 expressions = self.expressions(expression, flat=True) 2412 expressions = f" BY {expressions}" if expressions else "" 2413 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2415 def setitem_sql(self, expression: exp.SetItem) -> str: 2416 kind = self.sql(expression, "kind") 2417 kind = f"{kind} " if kind else "" 2418 this = self.sql(expression, "this") 2419 expressions = self.expressions(expression) 2420 collate = self.sql(expression, "collate") 2421 collate = f" COLLATE {collate}" if collate else "" 2422 global_ = "GLOBAL " if expression.args.get("global") else "" 2423 return f"{global_}{kind}{this}{expressions}{collate}"
2430 def queryband_sql(self, expression: exp.QueryBand) -> str: 2431 this = self.sql(expression, "this") 2432 update = " UPDATE" if expression.args.get("update") else "" 2433 scope = self.sql(expression, "scope") 2434 scope = f" FOR {scope}" if scope else "" 2435 2436 return f"QUERY_BAND = {this}{update}{scope}"
2441 def lock_sql(self, expression: exp.Lock) -> str: 2442 if not self.LOCKING_READS_SUPPORTED: 2443 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2444 return "" 2445 2446 update = expression.args["update"] 2447 key = expression.args.get("key") 2448 if update: 2449 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2450 else: 2451 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2452 expressions = self.expressions(expression, flat=True) 2453 expressions = f" OF {expressions}" if expressions else "" 2454 wait = expression.args.get("wait") 2455 2456 if wait is not None: 2457 if isinstance(wait, exp.Literal): 2458 wait = f" WAIT {self.sql(wait)}" 2459 else: 2460 wait = " NOWAIT" if wait else " SKIP LOCKED" 2461 2462 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2470 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2471 if self.dialect.ESCAPED_SEQUENCES: 2472 to_escaped = self.dialect.ESCAPED_SEQUENCES 2473 text = "".join( 2474 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2475 ) 2476 2477 return self._replace_line_breaks(text).replace( 2478 self.dialect.QUOTE_END, self._escaped_quote_end 2479 )
2481 def loaddata_sql(self, expression: exp.LoadData) -> str: 2482 local = " LOCAL" if expression.args.get("local") else "" 2483 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2484 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2485 this = f" INTO TABLE {self.sql(expression, 'this')}" 2486 partition = self.sql(expression, "partition") 2487 partition = f" {partition}" if partition else "" 2488 input_format = self.sql(expression, "input_format") 2489 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2490 serde = self.sql(expression, "serde") 2491 serde = f" SERDE {serde}" if serde else "" 2492 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2500 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2501 this = self.sql(expression, "this") 2502 this = f"{this} " if this else this 2503 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2504 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2506 def withfill_sql(self, expression: exp.WithFill) -> str: 2507 from_sql = self.sql(expression, "from") 2508 from_sql = f" FROM {from_sql}" if from_sql else "" 2509 to_sql = self.sql(expression, "to") 2510 to_sql = f" TO {to_sql}" if to_sql else "" 2511 step_sql = self.sql(expression, "step") 2512 step_sql = f" STEP {step_sql}" if step_sql else "" 2513 interpolated_values = [ 2514 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2515 if isinstance(e, exp.Alias) 2516 else self.sql(e, "this") 2517 for e in expression.args.get("interpolate") or [] 2518 ] 2519 interpolate = ( 2520 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2521 ) 2522 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2533 def ordered_sql(self, expression: exp.Ordered) -> str: 2534 desc = expression.args.get("desc") 2535 asc = not desc 2536 2537 nulls_first = expression.args.get("nulls_first") 2538 nulls_last = not nulls_first 2539 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2540 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2541 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2542 2543 this = self.sql(expression, "this") 2544 2545 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2546 nulls_sort_change = "" 2547 if nulls_first and ( 2548 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2549 ): 2550 nulls_sort_change = " NULLS FIRST" 2551 elif ( 2552 nulls_last 2553 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2554 and not nulls_are_last 2555 ): 2556 nulls_sort_change = " NULLS LAST" 2557 2558 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2559 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2560 window = expression.find_ancestor(exp.Window, exp.Select) 2561 if isinstance(window, exp.Window) and window.args.get("spec"): 2562 self.unsupported( 2563 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2564 ) 2565 nulls_sort_change = "" 2566 elif self.NULL_ORDERING_SUPPORTED is False and ( 2567 (asc and nulls_sort_change == " NULLS LAST") 2568 or (desc and nulls_sort_change == " NULLS FIRST") 2569 ): 2570 # BigQuery does not allow these ordering/nulls combinations when used under 2571 # an aggregation func or under a window containing one 2572 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2573 2574 if isinstance(ancestor, exp.Window): 2575 ancestor = ancestor.this 2576 if isinstance(ancestor, exp.AggFunc): 2577 self.unsupported( 2578 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2579 ) 2580 nulls_sort_change = "" 2581 elif self.NULL_ORDERING_SUPPORTED is None: 2582 if expression.this.is_int: 2583 self.unsupported( 2584 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2585 ) 2586 elif not isinstance(expression.this, exp.Rand): 2587 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2588 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2589 nulls_sort_change = "" 2590 2591 with_fill = self.sql(expression, "with_fill") 2592 with_fill = f" {with_fill}" if with_fill else "" 2593 2594 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2604 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2605 partition = self.partition_by_sql(expression) 2606 order = self.sql(expression, "order") 2607 measures = self.expressions(expression, key="measures") 2608 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2609 rows = self.sql(expression, "rows") 2610 rows = self.seg(rows) if rows else "" 2611 after = self.sql(expression, "after") 2612 after = self.seg(after) if after else "" 2613 pattern = self.sql(expression, "pattern") 2614 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2615 definition_sqls = [ 2616 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2617 for definition in expression.args.get("define", []) 2618 ] 2619 definitions = self.expressions(sqls=definition_sqls) 2620 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2621 body = "".join( 2622 ( 2623 partition, 2624 order, 2625 measures, 2626 rows, 2627 after, 2628 pattern, 2629 define, 2630 ) 2631 ) 2632 alias = self.sql(expression, "alias") 2633 alias = f" {alias}" if alias else "" 2634 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2636 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2637 limit = expression.args.get("limit") 2638 2639 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2640 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2641 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2642 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2643 2644 return csv( 2645 *sqls, 2646 *[self.sql(join) for join in expression.args.get("joins") or []], 2647 self.sql(expression, "match"), 2648 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2649 self.sql(expression, "prewhere"), 2650 self.sql(expression, "where"), 2651 self.sql(expression, "connect"), 2652 self.sql(expression, "group"), 2653 self.sql(expression, "having"), 2654 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2655 self.sql(expression, "order"), 2656 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2657 *self.after_limit_modifiers(expression), 2658 self.options_modifier(expression), 2659 self.for_modifiers(expression), 2660 sep="", 2661 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2675 def offset_limit_modifiers( 2676 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2677 ) -> t.List[str]: 2678 return [ 2679 self.sql(expression, "offset") if fetch else self.sql(limit), 2680 self.sql(limit) if fetch else self.sql(expression, "offset"), 2681 ]
2688 def select_sql(self, expression: exp.Select) -> str: 2689 into = expression.args.get("into") 2690 if not self.SUPPORTS_SELECT_INTO and into: 2691 into.pop() 2692 2693 hint = self.sql(expression, "hint") 2694 distinct = self.sql(expression, "distinct") 2695 distinct = f" {distinct}" if distinct else "" 2696 kind = self.sql(expression, "kind") 2697 2698 limit = expression.args.get("limit") 2699 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2700 top = self.limit_sql(limit, top=True) 2701 limit.pop() 2702 else: 2703 top = "" 2704 2705 expressions = self.expressions(expression) 2706 2707 if kind: 2708 if kind in self.SELECT_KINDS: 2709 kind = f" AS {kind}" 2710 else: 2711 if kind == "STRUCT": 2712 expressions = self.expressions( 2713 sqls=[ 2714 self.sql( 2715 exp.Struct( 2716 expressions=[ 2717 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2718 if isinstance(e, exp.Alias) 2719 else e 2720 for e in expression.expressions 2721 ] 2722 ) 2723 ) 2724 ] 2725 ) 2726 kind = "" 2727 2728 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2729 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2730 2731 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2732 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2733 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2734 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2735 sql = self.query_modifiers( 2736 expression, 2737 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2738 self.sql(expression, "into", comment=False), 2739 self.sql(expression, "from", comment=False), 2740 ) 2741 2742 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2743 if expression.args.get("with"): 2744 sql = self.maybe_comment(sql, expression) 2745 expression.pop_comments() 2746 2747 sql = self.prepend_ctes(expression, sql) 2748 2749 if not self.SUPPORTS_SELECT_INTO and into: 2750 if into.args.get("temporary"): 2751 table_kind = " TEMPORARY" 2752 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2753 table_kind = " UNLOGGED" 2754 else: 2755 table_kind = "" 2756 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2757 2758 return sql
2770 def star_sql(self, expression: exp.Star) -> str: 2771 except_ = self.expressions(expression, key="except", flat=True) 2772 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2773 replace = self.expressions(expression, key="replace", flat=True) 2774 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2775 rename = self.expressions(expression, key="rename", flat=True) 2776 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2777 return f"*{except_}{replace}{rename}"
2793 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2794 alias = self.sql(expression, "alias") 2795 alias = f"{sep}{alias}" if alias else "" 2796 sample = self.sql(expression, "sample") 2797 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2798 alias = f"{sample}{alias}" 2799 2800 # Set to None so it's not generated again by self.query_modifiers() 2801 expression.set("sample", None) 2802 2803 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2804 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2805 return self.prepend_ctes(expression, sql)
2811 def unnest_sql(self, expression: exp.Unnest) -> str: 2812 args = self.expressions(expression, flat=True) 2813 2814 alias = expression.args.get("alias") 2815 offset = expression.args.get("offset") 2816 2817 if self.UNNEST_WITH_ORDINALITY: 2818 if alias and isinstance(offset, exp.Expression): 2819 alias.append("columns", offset) 2820 2821 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2822 columns = alias.columns 2823 alias = self.sql(columns[0]) if columns else "" 2824 else: 2825 alias = self.sql(alias) 2826 2827 alias = f" AS {alias}" if alias else alias 2828 if self.UNNEST_WITH_ORDINALITY: 2829 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2830 else: 2831 if isinstance(offset, exp.Expression): 2832 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2833 elif offset: 2834 suffix = f"{alias} WITH OFFSET" 2835 else: 2836 suffix = alias 2837 2838 return f"UNNEST({args}){suffix}"
2847 def window_sql(self, expression: exp.Window) -> str: 2848 this = self.sql(expression, "this") 2849 partition = self.partition_by_sql(expression) 2850 order = expression.args.get("order") 2851 order = self.order_sql(order, flat=True) if order else "" 2852 spec = self.sql(expression, "spec") 2853 alias = self.sql(expression, "alias") 2854 over = self.sql(expression, "over") or "OVER" 2855 2856 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2857 2858 first = expression.args.get("first") 2859 if first is None: 2860 first = "" 2861 else: 2862 first = "FIRST" if first else "LAST" 2863 2864 if not partition and not order and not spec and alias: 2865 return f"{this} {alias}" 2866 2867 args = self.format_args( 2868 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2869 ) 2870 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2876 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2877 kind = self.sql(expression, "kind") 2878 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2879 end = ( 2880 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2881 or "CURRENT ROW" 2882 ) 2883 2884 window_spec = f"{kind} BETWEEN {start} AND {end}" 2885 2886 exclude = self.sql(expression, "exclude") 2887 if exclude: 2888 if self.SUPPORTS_WINDOW_EXCLUDE: 2889 window_spec += f" EXCLUDE {exclude}" 2890 else: 2891 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2892 2893 return window_spec
2900 def between_sql(self, expression: exp.Between) -> str: 2901 this = self.sql(expression, "this") 2902 low = self.sql(expression, "low") 2903 high = self.sql(expression, "high") 2904 symmetric = expression.args.get("symmetric") 2905 2906 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2907 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2908 2909 flag = ( 2910 " SYMMETRIC" 2911 if symmetric 2912 else " ASYMMETRIC" 2913 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2914 else "" # silently drop ASYMMETRIC – semantics identical 2915 ) 2916 return f"{this} BETWEEN{flag} {low} AND {high}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2918 def bracket_offset_expressions( 2919 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2920 ) -> t.List[exp.Expression]: 2921 return apply_index_offset( 2922 expression.this, 2923 expression.expressions, 2924 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2925 dialect=self.dialect, 2926 )
2939 def any_sql(self, expression: exp.Any) -> str: 2940 this = self.sql(expression, "this") 2941 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2942 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2943 this = self.wrap(this) 2944 return f"ANY{this}" 2945 return f"ANY {this}"
2950 def case_sql(self, expression: exp.Case) -> str: 2951 this = self.sql(expression, "this") 2952 statements = [f"CASE {this}" if this else "CASE"] 2953 2954 for e in expression.args["ifs"]: 2955 statements.append(f"WHEN {self.sql(e, 'this')}") 2956 statements.append(f"THEN {self.sql(e, 'true')}") 2957 2958 default = self.sql(expression, "default") 2959 2960 if default: 2961 statements.append(f"ELSE {default}") 2962 2963 statements.append("END") 2964 2965 if self.pretty and self.too_wide(statements): 2966 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2967 2968 return " ".join(statements)
2980 def extract_sql(self, expression: exp.Extract) -> str: 2981 from sqlglot.dialects.dialect import map_date_part 2982 2983 this = ( 2984 map_date_part(expression.this, self.dialect) 2985 if self.NORMALIZE_EXTRACT_DATE_PARTS 2986 else expression.this 2987 ) 2988 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2989 expression_sql = self.sql(expression, "expression") 2990 2991 return f"EXTRACT({this_sql} FROM {expression_sql})"
2993 def trim_sql(self, expression: exp.Trim) -> str: 2994 trim_type = self.sql(expression, "position") 2995 2996 if trim_type == "LEADING": 2997 func_name = "LTRIM" 2998 elif trim_type == "TRAILING": 2999 func_name = "RTRIM" 3000 else: 3001 func_name = "TRIM" 3002 3003 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
3005 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3006 args = expression.expressions 3007 if isinstance(expression, exp.ConcatWs): 3008 args = args[1:] # Skip the delimiter 3009 3010 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3011 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3012 3013 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3014 3015 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3016 if not e.type: 3017 from sqlglot.optimizer.annotate_types import annotate_types 3018 3019 e = annotate_types(e, dialect=self.dialect) 3020 3021 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3022 return e 3023 3024 return exp.func("coalesce", e, exp.Literal.string("")) 3025 3026 args = [_wrap_with_coalesce(e) for e in args] 3027 3028 return args
3030 def concat_sql(self, expression: exp.Concat) -> str: 3031 expressions = self.convert_concat_args(expression) 3032 3033 # Some dialects don't allow a single-argument CONCAT call 3034 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3035 return self.sql(expressions[0]) 3036 3037 return self.func("CONCAT", *expressions)
3048 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3049 expressions = self.expressions(expression, flat=True) 3050 expressions = f" ({expressions})" if expressions else "" 3051 reference = self.sql(expression, "reference") 3052 reference = f" {reference}" if reference else "" 3053 delete = self.sql(expression, "delete") 3054 delete = f" ON DELETE {delete}" if delete else "" 3055 update = self.sql(expression, "update") 3056 update = f" ON UPDATE {update}" if update else "" 3057 options = self.expressions(expression, key="options", flat=True, sep=" ") 3058 options = f" {options}" if options else "" 3059 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3061 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3062 expressions = self.expressions(expression, flat=True) 3063 include = self.sql(expression, "include") 3064 options = self.expressions(expression, key="options", flat=True, sep=" ") 3065 options = f" {options}" if options else "" 3066 return f"PRIMARY KEY ({expressions}){include}{options}"
3079 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3080 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3081 3082 if expression.args.get("escape"): 3083 path = self.escape_str(path) 3084 3085 if self.QUOTE_JSON_PATH: 3086 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3087 3088 return path
3090 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3091 if isinstance(expression, exp.JSONPathPart): 3092 transform = self.TRANSFORMS.get(expression.__class__) 3093 if not callable(transform): 3094 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3095 return "" 3096 3097 return transform(self, expression) 3098 3099 if isinstance(expression, int): 3100 return str(expression) 3101 3102 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3103 escaped = expression.replace("'", "\\'") 3104 escaped = f"\\'{expression}\\'" 3105 else: 3106 escaped = expression.replace('"', '\\"') 3107 escaped = f'"{escaped}"' 3108 3109 return escaped
3114 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3115 # Output the Teradata column FORMAT override. 3116 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3117 this = self.sql(expression, "this") 3118 fmt = self.sql(expression, "format") 3119 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3121 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3122 null_handling = expression.args.get("null_handling") 3123 null_handling = f" {null_handling}" if null_handling else "" 3124 3125 unique_keys = expression.args.get("unique_keys") 3126 if unique_keys is not None: 3127 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3128 else: 3129 unique_keys = "" 3130 3131 return_type = self.sql(expression, "return_type") 3132 return_type = f" RETURNING {return_type}" if return_type else "" 3133 encoding = self.sql(expression, "encoding") 3134 encoding = f" ENCODING {encoding}" if encoding else "" 3135 3136 return self.func( 3137 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3138 *expression.expressions, 3139 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3140 )
3145 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3146 null_handling = expression.args.get("null_handling") 3147 null_handling = f" {null_handling}" if null_handling else "" 3148 return_type = self.sql(expression, "return_type") 3149 return_type = f" RETURNING {return_type}" if return_type else "" 3150 strict = " STRICT" if expression.args.get("strict") else "" 3151 return self.func( 3152 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3153 )
3155 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3156 this = self.sql(expression, "this") 3157 order = self.sql(expression, "order") 3158 null_handling = expression.args.get("null_handling") 3159 null_handling = f" {null_handling}" if null_handling else "" 3160 return_type = self.sql(expression, "return_type") 3161 return_type = f" RETURNING {return_type}" if return_type else "" 3162 strict = " STRICT" if expression.args.get("strict") else "" 3163 return self.func( 3164 "JSON_ARRAYAGG", 3165 this, 3166 suffix=f"{order}{null_handling}{return_type}{strict})", 3167 )
3169 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3170 path = self.sql(expression, "path") 3171 path = f" PATH {path}" if path else "" 3172 nested_schema = self.sql(expression, "nested_schema") 3173 3174 if nested_schema: 3175 return f"NESTED{path} {nested_schema}" 3176 3177 this = self.sql(expression, "this") 3178 kind = self.sql(expression, "kind") 3179 kind = f" {kind}" if kind else "" 3180 return f"{this}{kind}{path}"
3185 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3186 this = self.sql(expression, "this") 3187 path = self.sql(expression, "path") 3188 path = f", {path}" if path else "" 3189 error_handling = expression.args.get("error_handling") 3190 error_handling = f" {error_handling}" if error_handling else "" 3191 empty_handling = expression.args.get("empty_handling") 3192 empty_handling = f" {empty_handling}" if empty_handling else "" 3193 schema = self.sql(expression, "schema") 3194 return self.func( 3195 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3196 )
3198 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3199 this = self.sql(expression, "this") 3200 kind = self.sql(expression, "kind") 3201 path = self.sql(expression, "path") 3202 path = f" {path}" if path else "" 3203 as_json = " AS JSON" if expression.args.get("as_json") else "" 3204 return f"{this} {kind}{path}{as_json}"
3206 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3207 this = self.sql(expression, "this") 3208 path = self.sql(expression, "path") 3209 path = f", {path}" if path else "" 3210 expressions = self.expressions(expression) 3211 with_ = ( 3212 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3213 if expressions 3214 else "" 3215 ) 3216 return f"OPENJSON({this}{path}){with_}"
3218 def in_sql(self, expression: exp.In) -> str: 3219 query = expression.args.get("query") 3220 unnest = expression.args.get("unnest") 3221 field = expression.args.get("field") 3222 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3223 3224 if query: 3225 in_sql = self.sql(query) 3226 elif unnest: 3227 in_sql = self.in_unnest_op(unnest) 3228 elif field: 3229 in_sql = self.sql(field) 3230 else: 3231 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3232 3233 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3238 def interval_sql(self, expression: exp.Interval) -> str: 3239 unit = self.sql(expression, "unit") 3240 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3241 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3242 unit = f" {unit}" if unit else "" 3243 3244 if self.SINGLE_STRING_INTERVAL: 3245 this = expression.this.name if expression.this else "" 3246 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3247 3248 this = self.sql(expression, "this") 3249 if this: 3250 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3251 this = f" {this}" if unwrapped else f" ({this})" 3252 3253 return f"INTERVAL{this}{unit}"
3258 def reference_sql(self, expression: exp.Reference) -> str: 3259 this = self.sql(expression, "this") 3260 expressions = self.expressions(expression, flat=True) 3261 expressions = f"({expressions})" if expressions else "" 3262 options = self.expressions(expression, key="options", flat=True, sep=" ") 3263 options = f" {options}" if options else "" 3264 return f"REFERENCES {this}{expressions}{options}"
3266 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3267 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3268 parent = expression.parent 3269 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3270 return self.func( 3271 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3272 )
3292 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3293 alias = expression.args["alias"] 3294 3295 parent = expression.parent 3296 pivot = parent and parent.parent 3297 3298 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3299 identifier_alias = isinstance(alias, exp.Identifier) 3300 literal_alias = isinstance(alias, exp.Literal) 3301 3302 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3303 alias.replace(exp.Literal.string(alias.output_name)) 3304 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3305 alias.replace(exp.to_identifier(alias.output_name)) 3306 3307 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3345 def connector_sql( 3346 self, 3347 expression: exp.Connector, 3348 op: str, 3349 stack: t.Optional[t.List[str | exp.Expression]] = None, 3350 ) -> str: 3351 if stack is not None: 3352 if expression.expressions: 3353 stack.append(self.expressions(expression, sep=f" {op} ")) 3354 else: 3355 stack.append(expression.right) 3356 if expression.comments and self.comments: 3357 for comment in expression.comments: 3358 if comment: 3359 op += f" /*{self.sanitize_comment(comment)}*/" 3360 stack.extend((op, expression.left)) 3361 return op 3362 3363 stack = [expression] 3364 sqls: t.List[str] = [] 3365 ops = set() 3366 3367 while stack: 3368 node = stack.pop() 3369 if isinstance(node, exp.Connector): 3370 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3371 else: 3372 sql = self.sql(node) 3373 if sqls and sqls[-1] in ops: 3374 sqls[-1] += f" {sql}" 3375 else: 3376 sqls.append(sql) 3377 3378 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3379 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3399 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3400 format_sql = self.sql(expression, "format") 3401 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3402 to_sql = self.sql(expression, "to") 3403 to_sql = f" {to_sql}" if to_sql else "" 3404 action = self.sql(expression, "action") 3405 action = f" {action}" if action else "" 3406 default = self.sql(expression, "default") 3407 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3408 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3422 def comment_sql(self, expression: exp.Comment) -> str: 3423 this = self.sql(expression, "this") 3424 kind = expression.args["kind"] 3425 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3426 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3427 expression_sql = self.sql(expression, "expression") 3428 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3430 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3431 this = self.sql(expression, "this") 3432 delete = " DELETE" if expression.args.get("delete") else "" 3433 recompress = self.sql(expression, "recompress") 3434 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3435 to_disk = self.sql(expression, "to_disk") 3436 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3437 to_volume = self.sql(expression, "to_volume") 3438 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3439 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3441 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3442 where = self.sql(expression, "where") 3443 group = self.sql(expression, "group") 3444 aggregates = self.expressions(expression, key="aggregates") 3445 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3446 3447 if not (where or group or aggregates) and len(expression.expressions) == 1: 3448 return f"TTL {self.expressions(expression, flat=True)}" 3449 3450 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3467 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3468 this = self.sql(expression, "this") 3469 3470 dtype = self.sql(expression, "dtype") 3471 if dtype: 3472 collate = self.sql(expression, "collate") 3473 collate = f" COLLATE {collate}" if collate else "" 3474 using = self.sql(expression, "using") 3475 using = f" USING {using}" if using else "" 3476 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3477 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3478 3479 default = self.sql(expression, "default") 3480 if default: 3481 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3482 3483 comment = self.sql(expression, "comment") 3484 if comment: 3485 return f"ALTER COLUMN {this} COMMENT {comment}" 3486 3487 visible = expression.args.get("visible") 3488 if visible: 3489 return f"ALTER COLUMN {this} SET {visible}" 3490 3491 allow_null = expression.args.get("allow_null") 3492 drop = expression.args.get("drop") 3493 3494 if not drop and not allow_null: 3495 self.unsupported("Unsupported ALTER COLUMN syntax") 3496 3497 if allow_null is not None: 3498 keyword = "DROP" if drop else "SET" 3499 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3500 3501 return f"ALTER COLUMN {this} DROP DEFAULT"
3517 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3518 compound = " COMPOUND" if expression.args.get("compound") else "" 3519 this = self.sql(expression, "this") 3520 expressions = self.expressions(expression, flat=True) 3521 expressions = f"({expressions})" if expressions else "" 3522 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3524 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3525 if not self.RENAME_TABLE_WITH_DB: 3526 # Remove db from tables 3527 expression = expression.transform( 3528 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3529 ).assert_is(exp.AlterRename) 3530 this = self.sql(expression, "this") 3531 to_kw = " TO" if include_to else "" 3532 return f"RENAME{to_kw} {this}"
3547 def alter_sql(self, expression: exp.Alter) -> str: 3548 actions = expression.args["actions"] 3549 3550 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3551 actions[0], exp.ColumnDef 3552 ): 3553 actions_sql = self.expressions(expression, key="actions", flat=True) 3554 actions_sql = f"ADD {actions_sql}" 3555 else: 3556 actions_list = [] 3557 for action in actions: 3558 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3559 action_sql = self.add_column_sql(action) 3560 else: 3561 action_sql = self.sql(action) 3562 if isinstance(action, exp.Query): 3563 action_sql = f"AS {action_sql}" 3564 3565 actions_list.append(action_sql) 3566 3567 actions_sql = self.format_args(*actions_list).lstrip("\n") 3568 3569 exists = " IF EXISTS" if expression.args.get("exists") else "" 3570 on_cluster = self.sql(expression, "cluster") 3571 on_cluster = f" {on_cluster}" if on_cluster else "" 3572 only = " ONLY" if expression.args.get("only") else "" 3573 options = self.expressions(expression, key="options") 3574 options = f", {options}" if options else "" 3575 kind = self.sql(expression, "kind") 3576 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3577 check = " WITH CHECK" if expression.args.get("check") else "" 3578 this = self.sql(expression, "this") 3579 this = f" {this}" if this else "" 3580 3581 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}"
3588 def add_column_sql(self, expression: exp.Expression) -> str: 3589 sql = self.sql(expression) 3590 if isinstance(expression, exp.Schema): 3591 column_text = " COLUMNS" 3592 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3593 column_text = " COLUMN" 3594 else: 3595 column_text = "" 3596 3597 return f"ADD{column_text} {sql}"
3607 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3608 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3609 location = self.sql(expression, "location") 3610 location = f" {location}" if location else "" 3611 return f"ADD {exists}{self.sql(expression.this)}{location}"
3613 def distinct_sql(self, expression: exp.Distinct) -> str: 3614 this = self.expressions(expression, flat=True) 3615 3616 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3617 case = exp.case() 3618 for arg in expression.expressions: 3619 case = case.when(arg.is_(exp.null()), exp.null()) 3620 this = self.sql(case.else_(f"({this})")) 3621 3622 this = f" {this}" if this else "" 3623 3624 on = self.sql(expression, "on") 3625 on = f" ON {on}" if on else "" 3626 return f"DISTINCT{this}{on}"
3655 def div_sql(self, expression: exp.Div) -> str: 3656 l, r = expression.left, expression.right 3657 3658 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3659 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3660 3661 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3662 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3663 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3664 3665 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3666 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3667 return self.sql( 3668 exp.cast( 3669 l / r, 3670 to=exp.DataType.Type.BIGINT, 3671 ) 3672 ) 3673 3674 return self.binary(expression, "/")
3791 def log_sql(self, expression: exp.Log) -> str: 3792 this = expression.this 3793 expr = expression.expression 3794 3795 if self.dialect.LOG_BASE_FIRST is False: 3796 this, expr = expr, this 3797 elif self.dialect.LOG_BASE_FIRST is None and expr: 3798 if this.name in ("2", "10"): 3799 return self.func(f"LOG{this.name}", expr) 3800 3801 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3802 3803 return self.func("LOG", this, expr)
3812 def binary(self, expression: exp.Binary, op: str) -> str: 3813 sqls: t.List[str] = [] 3814 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3815 binary_type = type(expression) 3816 3817 while stack: 3818 node = stack.pop() 3819 3820 if type(node) is binary_type: 3821 op_func = node.args.get("operator") 3822 if op_func: 3823 op = f"OPERATOR({self.sql(op_func)})" 3824 3825 stack.append(node.right) 3826 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3827 stack.append(node.left) 3828 else: 3829 sqls.append(self.sql(node)) 3830 3831 return "".join(sqls)
3840 def function_fallback_sql(self, expression: exp.Func) -> str: 3841 args = [] 3842 3843 for key in expression.arg_types: 3844 arg_value = expression.args.get(key) 3845 3846 if isinstance(arg_value, list): 3847 for value in arg_value: 3848 args.append(value) 3849 elif arg_value is not None: 3850 args.append(arg_value) 3851 3852 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3853 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3854 else: 3855 name = expression.sql_name() 3856 3857 return self.func(name, *args)
def
func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
3859 def func( 3860 self, 3861 name: str, 3862 *args: t.Optional[exp.Expression | str], 3863 prefix: str = "(", 3864 suffix: str = ")", 3865 normalize: bool = True, 3866 ) -> str: 3867 name = self.normalize_func(name) if normalize else name 3868 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3870 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3871 arg_sqls = tuple( 3872 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3873 ) 3874 if self.pretty and self.too_wide(arg_sqls): 3875 return self.indent( 3876 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3877 ) 3878 return sep.join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
3883 def format_time( 3884 self, 3885 expression: exp.Expression, 3886 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3887 inverse_time_trie: t.Optional[t.Dict] = None, 3888 ) -> t.Optional[str]: 3889 return format_time( 3890 self.sql(expression, "format"), 3891 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3892 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3893 )
def
expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
3895 def expressions( 3896 self, 3897 expression: t.Optional[exp.Expression] = None, 3898 key: t.Optional[str] = None, 3899 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3900 flat: bool = False, 3901 indent: bool = True, 3902 skip_first: bool = False, 3903 skip_last: bool = False, 3904 sep: str = ", ", 3905 prefix: str = "", 3906 dynamic: bool = False, 3907 new_line: bool = False, 3908 ) -> str: 3909 expressions = expression.args.get(key or "expressions") if expression else sqls 3910 3911 if not expressions: 3912 return "" 3913 3914 if flat: 3915 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3916 3917 num_sqls = len(expressions) 3918 result_sqls = [] 3919 3920 for i, e in enumerate(expressions): 3921 sql = self.sql(e, comment=False) 3922 if not sql: 3923 continue 3924 3925 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3926 3927 if self.pretty: 3928 if self.leading_comma: 3929 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3930 else: 3931 result_sqls.append( 3932 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3933 ) 3934 else: 3935 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3936 3937 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3938 if new_line: 3939 result_sqls.insert(0, "") 3940 result_sqls.append("") 3941 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3942 else: 3943 result_sql = "".join(result_sqls) 3944 3945 return ( 3946 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3947 if indent 3948 else result_sql 3949 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3951 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3952 flat = flat or isinstance(expression.parent, exp.Properties) 3953 expressions_sql = self.expressions(expression, flat=flat) 3954 if flat: 3955 return f"{op} {expressions_sql}" 3956 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3958 def naked_property(self, expression: exp.Property) -> str: 3959 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3960 if not property_name: 3961 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3962 return f"{property_name} {self.sql(expression, 'this')}"
3970 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3971 this = self.sql(expression, "this") 3972 expressions = self.no_identify(self.expressions, expression) 3973 expressions = ( 3974 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3975 ) 3976 return f"{this}{expressions}" if expressions.strip() != "" else this
3986 def when_sql(self, expression: exp.When) -> str: 3987 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3988 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3989 condition = self.sql(expression, "condition") 3990 condition = f" AND {condition}" if condition else "" 3991 3992 then_expression = expression.args.get("then") 3993 if isinstance(then_expression, exp.Insert): 3994 this = self.sql(then_expression, "this") 3995 this = f"INSERT {this}" if this else "INSERT" 3996 then = self.sql(then_expression, "expression") 3997 then = f"{this} VALUES {then}" if then else this 3998 elif isinstance(then_expression, exp.Update): 3999 if isinstance(then_expression.args.get("expressions"), exp.Star): 4000 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4001 else: 4002 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4003 else: 4004 then = self.sql(then_expression) 4005 return f"WHEN {matched}{source}{condition} THEN {then}"
4010 def merge_sql(self, expression: exp.Merge) -> str: 4011 table = expression.this 4012 table_alias = "" 4013 4014 hints = table.args.get("hints") 4015 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4016 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4017 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4018 4019 this = self.sql(table) 4020 using = f"USING {self.sql(expression, 'using')}" 4021 on = f"ON {self.sql(expression, 'on')}" 4022 whens = self.sql(expression, "whens") 4023 4024 returning = self.sql(expression, "returning") 4025 if returning: 4026 whens = f"{whens}{returning}" 4027 4028 sep = self.sep() 4029 4030 return self.prepend_ctes( 4031 expression, 4032 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4033 )
4039 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4040 if not self.SUPPORTS_TO_NUMBER: 4041 self.unsupported("Unsupported TO_NUMBER function") 4042 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4043 4044 fmt = expression.args.get("format") 4045 if not fmt: 4046 self.unsupported("Conversion format is required for TO_NUMBER") 4047 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4048 4049 return self.func("TO_NUMBER", expression.this, fmt)
4051 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4052 this = self.sql(expression, "this") 4053 kind = self.sql(expression, "kind") 4054 settings_sql = self.expressions(expression, key="settings", sep=" ") 4055 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4056 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4077 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4078 expressions = self.expressions(expression, flat=True) 4079 expressions = f" {self.wrap(expressions)}" if expressions else "" 4080 buckets = self.sql(expression, "buckets") 4081 kind = self.sql(expression, "kind") 4082 buckets = f" BUCKETS {buckets}" if buckets else "" 4083 order = self.sql(expression, "order") 4084 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4089 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4090 expressions = self.expressions(expression, key="expressions", flat=True) 4091 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4092 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4093 buckets = self.sql(expression, "buckets") 4094 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4096 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4097 this = self.sql(expression, "this") 4098 having = self.sql(expression, "having") 4099 4100 if having: 4101 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4102 4103 return self.func("ANY_VALUE", this)
4105 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4106 transform = self.func("TRANSFORM", *expression.expressions) 4107 row_format_before = self.sql(expression, "row_format_before") 4108 row_format_before = f" {row_format_before}" if row_format_before else "" 4109 record_writer = self.sql(expression, "record_writer") 4110 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4111 using = f" USING {self.sql(expression, 'command_script')}" 4112 schema = self.sql(expression, "schema") 4113 schema = f" AS {schema}" if schema else "" 4114 row_format_after = self.sql(expression, "row_format_after") 4115 row_format_after = f" {row_format_after}" if row_format_after else "" 4116 record_reader = self.sql(expression, "record_reader") 4117 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4118 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4120 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4121 key_block_size = self.sql(expression, "key_block_size") 4122 if key_block_size: 4123 return f"KEY_BLOCK_SIZE = {key_block_size}" 4124 4125 using = self.sql(expression, "using") 4126 if using: 4127 return f"USING {using}" 4128 4129 parser = self.sql(expression, "parser") 4130 if parser: 4131 return f"WITH PARSER {parser}" 4132 4133 comment = self.sql(expression, "comment") 4134 if comment: 4135 return f"COMMENT {comment}" 4136 4137 visible = expression.args.get("visible") 4138 if visible is not None: 4139 return "VISIBLE" if visible else "INVISIBLE" 4140 4141 engine_attr = self.sql(expression, "engine_attr") 4142 if engine_attr: 4143 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4144 4145 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4146 if secondary_engine_attr: 4147 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4148 4149 self.unsupported("Unsupported index constraint option.") 4150 return ""
4156 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4157 kind = self.sql(expression, "kind") 4158 kind = f"{kind} INDEX" if kind else "INDEX" 4159 this = self.sql(expression, "this") 4160 this = f" {this}" if this else "" 4161 index_type = self.sql(expression, "index_type") 4162 index_type = f" USING {index_type}" if index_type else "" 4163 expressions = self.expressions(expression, flat=True) 4164 expressions = f" ({expressions})" if expressions else "" 4165 options = self.expressions(expression, key="options", sep=" ") 4166 options = f" {options}" if options else "" 4167 return f"{kind}{this}{index_type}{expressions}{options}"
4169 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4170 if self.NVL2_SUPPORTED: 4171 return self.function_fallback_sql(expression) 4172 4173 case = exp.Case().when( 4174 expression.this.is_(exp.null()).not_(copy=False), 4175 expression.args["true"], 4176 copy=False, 4177 ) 4178 else_cond = expression.args.get("false") 4179 if else_cond: 4180 case.else_(else_cond, copy=False) 4181 4182 return self.sql(case)
4184 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4185 this = self.sql(expression, "this") 4186 expr = self.sql(expression, "expression") 4187 iterator = self.sql(expression, "iterator") 4188 condition = self.sql(expression, "condition") 4189 condition = f" IF {condition}" if condition else "" 4190 return f"{this} FOR {expr} IN {iterator}{condition}"
4198 def predict_sql(self, expression: exp.Predict) -> str: 4199 model = self.sql(expression, "this") 4200 model = f"MODEL {model}" 4201 table = self.sql(expression, "expression") 4202 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4203 parameters = self.sql(expression, "params_struct") 4204 return self.func("PREDICT", model, table, parameters or None)
4206 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4207 model = self.sql(expression, "this") 4208 model = f"MODEL {model}" 4209 table = self.sql(expression, "expression") 4210 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4211 parameters = self.sql(expression, "params_struct") 4212 return self.func("GENERATE_EMBEDDING", model, table, parameters or None)
4214 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4215 this_sql = self.sql(expression, "this") 4216 if isinstance(expression.this, exp.Table): 4217 this_sql = f"TABLE {this_sql}" 4218 4219 return self.func( 4220 "FEATURES_AT_TIME", 4221 this_sql, 4222 expression.args.get("time"), 4223 expression.args.get("num_rows"), 4224 expression.args.get("ignore_feature_nulls"), 4225 )
4227 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4228 this_sql = self.sql(expression, "this") 4229 if isinstance(expression.this, exp.Table): 4230 this_sql = f"TABLE {this_sql}" 4231 4232 query_table = self.sql(expression, "query_table") 4233 if isinstance(expression.args["query_table"], exp.Table): 4234 query_table = f"TABLE {query_table}" 4235 4236 return self.func( 4237 "VECTOR_SEARCH", 4238 this_sql, 4239 expression.args.get("column_to_search"), 4240 query_table, 4241 expression.args.get("query_column_to_search"), 4242 expression.args.get("top_k"), 4243 expression.args.get("distance_type"), 4244 expression.args.get("options"), 4245 )
4257 def toarray_sql(self, expression: exp.ToArray) -> str: 4258 arg = expression.this 4259 if not arg.type: 4260 from sqlglot.optimizer.annotate_types import annotate_types 4261 4262 arg = annotate_types(arg, dialect=self.dialect) 4263 4264 if arg.is_type(exp.DataType.Type.ARRAY): 4265 return self.sql(arg) 4266 4267 cond_for_null = arg.is_(exp.null()) 4268 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4270 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4271 this = expression.this 4272 time_format = self.format_time(expression) 4273 4274 if time_format: 4275 return self.sql( 4276 exp.cast( 4277 exp.StrToTime(this=this, format=expression.args["format"]), 4278 exp.DataType.Type.TIME, 4279 ) 4280 ) 4281 4282 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4283 return self.sql(this) 4284 4285 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4287 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4288 this = expression.this 4289 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4290 return self.sql(this) 4291 4292 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4294 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4295 this = expression.this 4296 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4297 return self.sql(this) 4298 4299 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4301 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4302 this = expression.this 4303 time_format = self.format_time(expression) 4304 4305 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4306 return self.sql( 4307 exp.cast( 4308 exp.StrToTime(this=this, format=expression.args["format"]), 4309 exp.DataType.Type.DATE, 4310 ) 4311 ) 4312 4313 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4314 return self.sql(this) 4315 4316 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4328 def lastday_sql(self, expression: exp.LastDay) -> str: 4329 if self.LAST_DAY_SUPPORTS_DATE_PART: 4330 return self.function_fallback_sql(expression) 4331 4332 unit = expression.text("unit") 4333 if unit and unit != "MONTH": 4334 self.unsupported("Date parts are not supported in LAST_DAY.") 4335 4336 return self.func("LAST_DAY", expression.this)
4345 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4346 if self.CAN_IMPLEMENT_ARRAY_ANY: 4347 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4348 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4349 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4350 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4351 4352 from sqlglot.dialects import Dialect 4353 4354 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4355 if self.dialect.__class__ != Dialect: 4356 self.unsupported("ARRAY_ANY is unsupported") 4357 4358 return self.function_fallback_sql(expression)
4360 def struct_sql(self, expression: exp.Struct) -> str: 4361 expression.set( 4362 "expressions", 4363 [ 4364 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4365 if isinstance(e, exp.PropertyEQ) 4366 else e 4367 for e in expression.expressions 4368 ], 4369 ) 4370 4371 return self.function_fallback_sql(expression)
4379 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4380 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4381 tables = f" {self.expressions(expression)}" 4382 4383 exists = " IF EXISTS" if expression.args.get("exists") else "" 4384 4385 on_cluster = self.sql(expression, "cluster") 4386 on_cluster = f" {on_cluster}" if on_cluster else "" 4387 4388 identity = self.sql(expression, "identity") 4389 identity = f" {identity} IDENTITY" if identity else "" 4390 4391 option = self.sql(expression, "option") 4392 option = f" {option}" if option else "" 4393 4394 partition = self.sql(expression, "partition") 4395 partition = f" {partition}" if partition else "" 4396 4397 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4401 def convert_sql(self, expression: exp.Convert) -> str: 4402 to = expression.this 4403 value = expression.expression 4404 style = expression.args.get("style") 4405 safe = expression.args.get("safe") 4406 strict = expression.args.get("strict") 4407 4408 if not to or not value: 4409 return "" 4410 4411 # Retrieve length of datatype and override to default if not specified 4412 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4413 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4414 4415 transformed: t.Optional[exp.Expression] = None 4416 cast = exp.Cast if strict else exp.TryCast 4417 4418 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4419 if isinstance(style, exp.Literal) and style.is_int: 4420 from sqlglot.dialects.tsql import TSQL 4421 4422 style_value = style.name 4423 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4424 if not converted_style: 4425 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4426 4427 fmt = exp.Literal.string(converted_style) 4428 4429 if to.this == exp.DataType.Type.DATE: 4430 transformed = exp.StrToDate(this=value, format=fmt) 4431 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4432 transformed = exp.StrToTime(this=value, format=fmt) 4433 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4434 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4435 elif to.this == exp.DataType.Type.TEXT: 4436 transformed = exp.TimeToStr(this=value, format=fmt) 4437 4438 if not transformed: 4439 transformed = cast(this=value, to=to, safe=safe) 4440 4441 return self.sql(transformed)
4509 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4510 option = self.sql(expression, "this") 4511 4512 if expression.expressions: 4513 upper = option.upper() 4514 4515 # Snowflake FILE_FORMAT options are separated by whitespace 4516 sep = " " if upper == "FILE_FORMAT" else ", " 4517 4518 # Databricks copy/format options do not set their list of values with EQ 4519 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4520 values = self.expressions(expression, flat=True, sep=sep) 4521 return f"{option}{op}({values})" 4522 4523 value = self.sql(expression, "expression") 4524 4525 if not value: 4526 return option 4527 4528 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4529 4530 return f"{option}{op}{value}"
4532 def credentials_sql(self, expression: exp.Credentials) -> str: 4533 cred_expr = expression.args.get("credentials") 4534 if isinstance(cred_expr, exp.Literal): 4535 # Redshift case: CREDENTIALS <string> 4536 credentials = self.sql(expression, "credentials") 4537 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4538 else: 4539 # Snowflake case: CREDENTIALS = (...) 4540 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4541 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4542 4543 storage = self.sql(expression, "storage") 4544 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4545 4546 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4547 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4548 4549 iam_role = self.sql(expression, "iam_role") 4550 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4551 4552 region = self.sql(expression, "region") 4553 region = f" REGION {region}" if region else "" 4554 4555 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4557 def copy_sql(self, expression: exp.Copy) -> str: 4558 this = self.sql(expression, "this") 4559 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4560 4561 credentials = self.sql(expression, "credentials") 4562 credentials = self.seg(credentials) if credentials else "" 4563 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4564 files = self.expressions(expression, key="files", flat=True) 4565 4566 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4567 params = self.expressions( 4568 expression, 4569 key="params", 4570 sep=sep, 4571 new_line=True, 4572 skip_last=True, 4573 skip_first=True, 4574 indent=self.COPY_PARAMS_ARE_WRAPPED, 4575 ) 4576 4577 if params: 4578 if self.COPY_PARAMS_ARE_WRAPPED: 4579 params = f" WITH ({params})" 4580 elif not self.pretty: 4581 params = f" {params}" 4582 4583 return f"COPY{this}{kind} {files}{credentials}{params}"
4588 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4589 on_sql = "ON" if expression.args.get("on") else "OFF" 4590 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4591 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4592 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4593 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4594 4595 if filter_col or retention_period: 4596 on_sql = self.func("ON", filter_col, retention_period) 4597 4598 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4600 def maskingpolicycolumnconstraint_sql( 4601 self, expression: exp.MaskingPolicyColumnConstraint 4602 ) -> str: 4603 this = self.sql(expression, "this") 4604 expressions = self.expressions(expression, flat=True) 4605 expressions = f" USING ({expressions})" if expressions else "" 4606 return f"MASKING POLICY {this}{expressions}"
4616 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4617 this = self.sql(expression, "this") 4618 expr = expression.expression 4619 4620 if isinstance(expr, exp.Func): 4621 # T-SQL's CLR functions are case sensitive 4622 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4623 else: 4624 expr = self.sql(expression, "expression") 4625 4626 return self.scope_resolution(expr, this)
4634 def rand_sql(self, expression: exp.Rand) -> str: 4635 lower = self.sql(expression, "lower") 4636 upper = self.sql(expression, "upper") 4637 4638 if lower and upper: 4639 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4640 return self.func("RAND", expression.this)
4642 def changes_sql(self, expression: exp.Changes) -> str: 4643 information = self.sql(expression, "information") 4644 information = f"INFORMATION => {information}" 4645 at_before = self.sql(expression, "at_before") 4646 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4647 end = self.sql(expression, "end") 4648 end = f"{self.seg('')}{end}" if end else "" 4649 4650 return f"CHANGES ({information}){at_before}{end}"
4652 def pad_sql(self, expression: exp.Pad) -> str: 4653 prefix = "L" if expression.args.get("is_left") else "R" 4654 4655 fill_pattern = self.sql(expression, "fill_pattern") or None 4656 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4657 fill_pattern = "' '" 4658 4659 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4665 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4666 generate_series = exp.GenerateSeries(**expression.args) 4667 4668 parent = expression.parent 4669 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4670 parent = parent.parent 4671 4672 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4673 return self.sql(exp.Unnest(expressions=[generate_series])) 4674 4675 if isinstance(parent, exp.Select): 4676 self.unsupported("GenerateSeries projection unnesting is not supported.") 4677 4678 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4680 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4681 exprs = expression.expressions 4682 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4683 if len(exprs) == 0: 4684 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4685 else: 4686 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4687 else: 4688 rhs = self.expressions(expression) # type: ignore 4689 4690 return self.func(name, expression.this, rhs or None)
4692 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4693 if self.SUPPORTS_CONVERT_TIMEZONE: 4694 return self.function_fallback_sql(expression) 4695 4696 source_tz = expression.args.get("source_tz") 4697 target_tz = expression.args.get("target_tz") 4698 timestamp = expression.args.get("timestamp") 4699 4700 if source_tz and timestamp: 4701 timestamp = exp.AtTimeZone( 4702 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4703 ) 4704 4705 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4706 4707 return self.sql(expr)
4709 def json_sql(self, expression: exp.JSON) -> str: 4710 this = self.sql(expression, "this") 4711 this = f" {this}" if this else "" 4712 4713 _with = expression.args.get("with") 4714 4715 if _with is None: 4716 with_sql = "" 4717 elif not _with: 4718 with_sql = " WITHOUT" 4719 else: 4720 with_sql = " WITH" 4721 4722 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4723 4724 return f"JSON{this}{with_sql}{unique_sql}"
4726 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4727 def _generate_on_options(arg: t.Any) -> str: 4728 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4729 4730 path = self.sql(expression, "path") 4731 returning = self.sql(expression, "returning") 4732 returning = f" RETURNING {returning}" if returning else "" 4733 4734 on_condition = self.sql(expression, "on_condition") 4735 on_condition = f" {on_condition}" if on_condition else "" 4736 4737 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4739 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4740 else_ = "ELSE " if expression.args.get("else_") else "" 4741 condition = self.sql(expression, "expression") 4742 condition = f"WHEN {condition} THEN " if condition else else_ 4743 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4744 return f"{condition}{insert}"
4752 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4753 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4754 empty = expression.args.get("empty") 4755 empty = ( 4756 f"DEFAULT {empty} ON EMPTY" 4757 if isinstance(empty, exp.Expression) 4758 else self.sql(expression, "empty") 4759 ) 4760 4761 error = expression.args.get("error") 4762 error = ( 4763 f"DEFAULT {error} ON ERROR" 4764 if isinstance(error, exp.Expression) 4765 else self.sql(expression, "error") 4766 ) 4767 4768 if error and empty: 4769 error = ( 4770 f"{empty} {error}" 4771 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4772 else f"{error} {empty}" 4773 ) 4774 empty = "" 4775 4776 null = self.sql(expression, "null") 4777 4778 return f"{empty}{error}{null}"
4784 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4785 this = self.sql(expression, "this") 4786 path = self.sql(expression, "path") 4787 4788 passing = self.expressions(expression, "passing") 4789 passing = f" PASSING {passing}" if passing else "" 4790 4791 on_condition = self.sql(expression, "on_condition") 4792 on_condition = f" {on_condition}" if on_condition else "" 4793 4794 path = f"{path}{passing}{on_condition}" 4795 4796 return self.func("JSON_EXISTS", this, path)
4798 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4799 array_agg = self.function_fallback_sql(expression) 4800 4801 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4802 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4803 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4804 parent = expression.parent 4805 if isinstance(parent, exp.Filter): 4806 parent_cond = parent.expression.this 4807 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4808 else: 4809 this = expression.this 4810 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4811 if this.find(exp.Column): 4812 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4813 this_sql = ( 4814 self.expressions(this) 4815 if isinstance(this, exp.Distinct) 4816 else self.sql(expression, "this") 4817 ) 4818 4819 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4820 4821 return array_agg
4894 def overlay_sql(self, expression: exp.Overlay): 4895 this = self.sql(expression, "this") 4896 expr = self.sql(expression, "expression") 4897 from_sql = self.sql(expression, "from") 4898 for_sql = self.sql(expression, "for") 4899 for_sql = f" FOR {for_sql}" if for_sql else "" 4900 4901 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4907 def string_sql(self, expression: exp.String) -> str: 4908 this = expression.this 4909 zone = expression.args.get("zone") 4910 4911 if zone: 4912 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4913 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4914 # set for source_tz to transpile the time conversion before the STRING cast 4915 this = exp.ConvertTimezone( 4916 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4917 ) 4918 4919 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4929 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4930 filler = self.sql(expression, "this") 4931 filler = f" {filler}" if filler else "" 4932 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4933 return f"TRUNCATE{filler} {with_count}"
4935 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4936 if self.SUPPORTS_UNIX_SECONDS: 4937 return self.function_fallback_sql(expression) 4938 4939 start_ts = exp.cast( 4940 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4941 ) 4942 4943 return self.sql( 4944 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4945 )
4947 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4948 dim = expression.expression 4949 4950 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4951 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4952 if not (dim.is_int and dim.name == "1"): 4953 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4954 dim = None 4955 4956 # If dimension is required but not specified, default initialize it 4957 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4958 dim = exp.Literal.number(1) 4959 4960 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4962 def attach_sql(self, expression: exp.Attach) -> str: 4963 this = self.sql(expression, "this") 4964 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4965 expressions = self.expressions(expression) 4966 expressions = f" ({expressions})" if expressions else "" 4967 4968 return f"ATTACH{exists_sql} {this}{expressions}"
4970 def detach_sql(self, expression: exp.Detach) -> str: 4971 this = self.sql(expression, "this") 4972 # the DATABASE keyword is required if IF EXISTS is set 4973 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4974 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4975 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4976 4977 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4990 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4991 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4992 encode = f"{encode} {self.sql(expression, 'this')}" 4993 4994 properties = expression.args.get("properties") 4995 if properties: 4996 encode = f"{encode} {self.properties(properties)}" 4997 4998 return encode
5000 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5001 this = self.sql(expression, "this") 5002 include = f"INCLUDE {this}" 5003 5004 column_def = self.sql(expression, "column_def") 5005 if column_def: 5006 include = f"{include} {column_def}" 5007 5008 alias = self.sql(expression, "alias") 5009 if alias: 5010 include = f"{include} AS {alias}" 5011 5012 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5024 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5025 partitions = self.expressions(expression, "partition_expressions") 5026 create = self.expressions(expression, "create_expressions") 5027 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5029 def partitionbyrangepropertydynamic_sql( 5030 self, expression: exp.PartitionByRangePropertyDynamic 5031 ) -> str: 5032 start = self.sql(expression, "start") 5033 end = self.sql(expression, "end") 5034 5035 every = expression.args["every"] 5036 if isinstance(every, exp.Interval) and every.this.is_string: 5037 every.this.replace(exp.Literal.number(every.name)) 5038 5039 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5052 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5053 kind = self.sql(expression, "kind") 5054 option = self.sql(expression, "option") 5055 option = f" {option}" if option else "" 5056 this = self.sql(expression, "this") 5057 this = f" {this}" if this else "" 5058 columns = self.expressions(expression) 5059 columns = f" {columns}" if columns else "" 5060 return f"{kind}{option} STATISTICS{this}{columns}"
5062 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5063 this = self.sql(expression, "this") 5064 columns = self.expressions(expression) 5065 inner_expression = self.sql(expression, "expression") 5066 inner_expression = f" {inner_expression}" if inner_expression else "" 5067 update_options = self.sql(expression, "update_options") 5068 update_options = f" {update_options} UPDATE" if update_options else "" 5069 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5080 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5081 kind = self.sql(expression, "kind") 5082 this = self.sql(expression, "this") 5083 this = f" {this}" if this else "" 5084 inner_expression = self.sql(expression, "expression") 5085 return f"VALIDATE {kind}{this}{inner_expression}"
5087 def analyze_sql(self, expression: exp.Analyze) -> str: 5088 options = self.expressions(expression, key="options", sep=" ") 5089 options = f" {options}" if options else "" 5090 kind = self.sql(expression, "kind") 5091 kind = f" {kind}" if kind else "" 5092 this = self.sql(expression, "this") 5093 this = f" {this}" if this else "" 5094 mode = self.sql(expression, "mode") 5095 mode = f" {mode}" if mode else "" 5096 properties = self.sql(expression, "properties") 5097 properties = f" {properties}" if properties else "" 5098 partition = self.sql(expression, "partition") 5099 partition = f" {partition}" if partition else "" 5100 inner_expression = self.sql(expression, "expression") 5101 inner_expression = f" {inner_expression}" if inner_expression else "" 5102 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5104 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5105 this = self.sql(expression, "this") 5106 namespaces = self.expressions(expression, key="namespaces") 5107 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5108 passing = self.expressions(expression, key="passing") 5109 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5110 columns = self.expressions(expression, key="columns") 5111 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5112 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5113 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5119 def export_sql(self, expression: exp.Export) -> str: 5120 this = self.sql(expression, "this") 5121 connection = self.sql(expression, "connection") 5122 connection = f"WITH CONNECTION {connection} " if connection else "" 5123 options = self.sql(expression, "options") 5124 return f"EXPORT DATA {connection}{options} AS {this}"
5129 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5130 variable = self.sql(expression, "this") 5131 default = self.sql(expression, "default") 5132 default = f" = {default}" if default else "" 5133 5134 kind = self.sql(expression, "kind") 5135 if isinstance(expression.args.get("kind"), exp.Schema): 5136 kind = f"TABLE {kind}" 5137 5138 return f"{variable} AS {kind}{default}"
5140 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5141 kind = self.sql(expression, "kind") 5142 this = self.sql(expression, "this") 5143 set = self.sql(expression, "expression") 5144 using = self.sql(expression, "using") 5145 using = f" USING {using}" if using else "" 5146 5147 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5148 5149 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5168 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5169 # Snowflake GET/PUT statements: 5170 # PUT <file> <internalStage> <properties> 5171 # GET <internalStage> <file> <properties> 5172 props = expression.args.get("properties") 5173 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5174 this = self.sql(expression, "this") 5175 target = self.sql(expression, "target") 5176 5177 if isinstance(expression, exp.Put): 5178 return f"PUT {this} {target}{props_sql}" 5179 else: 5180 return f"GET {target} {this}{props_sql}"
5188 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5189 if self.SUPPORTS_DECODE_CASE: 5190 return self.func("DECODE", *expression.expressions) 5191 5192 expression, *expressions = expression.expressions 5193 5194 ifs = [] 5195 for search, result in zip(expressions[::2], expressions[1::2]): 5196 if isinstance(search, exp.Literal): 5197 ifs.append(exp.If(this=expression.eq(search), true=result)) 5198 elif isinstance(search, exp.Null): 5199 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5200 else: 5201 if isinstance(search, exp.Binary): 5202 search = exp.paren(search) 5203 5204 cond = exp.or_( 5205 expression.eq(search), 5206 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5207 copy=False, 5208 ) 5209 ifs.append(exp.If(this=cond, true=result)) 5210 5211 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5212 return self.sql(case)
5214 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5215 this = self.sql(expression, "this") 5216 this = self.seg(this, sep="") 5217 dimensions = self.expressions( 5218 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5219 ) 5220 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5221 metrics = self.expressions( 5222 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5223 ) 5224 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5225 where = self.sql(expression, "where") 5226 where = self.seg(f"WHERE {where}") if where else "" 5227 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
5229 def getextract_sql(self, expression: exp.GetExtract) -> str: 5230 this = expression.this 5231 expr = expression.expression 5232 5233 if not this.type or not expression.type: 5234 from sqlglot.optimizer.annotate_types import annotate_types 5235 5236 this = annotate_types(this, dialect=self.dialect) 5237 5238 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5239 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5240 5241 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5258 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5259 method = self.sql(expression, "method") 5260 kind = expression.args.get("kind") 5261 if not kind: 5262 return f"REFRESH {method}" 5263 5264 every = self.sql(expression, "every") 5265 unit = self.sql(expression, "unit") 5266 every = f" EVERY {every} {unit}" if every else "" 5267 starts = self.sql(expression, "starts") 5268 starts = f" STARTS {starts}" if starts else "" 5269 5270 return f"REFRESH {method} ON {kind}{every}{starts}"